/**
 * anime_list.cpp: defines the anime list page
 * and widgets.
 *
 * much of this file is based around
 * Qt's original QTabWidget implementation, because
 * I needed a somewhat native way to create a tabbed
 * widget with only one subwidget that worked exactly
 * like a native tabbed widget.
 **/
#include "gui/pages/anime_list.h"
#include "core/anime.h"
#include "core/anime_db.h"
#include "core/session.h"
#include "core/strings.h"
#include "core/time.h"
#include "gui/dialog/information.h"
#include "gui/translate/anime.h"
#include "library/library.h"
#include "services/services.h"

#include <QDate>
#include <QDebug>
#include <QDesktopServices>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QMenu>
#include <QProgressBar>
#include <QRunnable>
#include <QShortcut>
#include <QStylePainter>
#include <QStyledItemDelegate>
#include <QThreadPool>
#include <QTreeView>
#include <QUrl>

#include <iostream>
#include <vector>

AnimeListPageUpdateEntryThread::AnimeListPageUpdateEntryThread(QObject *parent) : QThread(parent)
{
}

void AnimeListPageUpdateEntryThread::AddToQueue(int id)
{
	const std::lock_guard<std::mutex> guard(queue_mutex_);
	queue_.push(id);
}

/* processes the queue... */
void AnimeListPageUpdateEntryThread::run()
{
	queue_mutex_.lock();
	while (!queue_.empty() && !isInterruptionRequested()) {
		int id = queue_.front();

		/* unlock the mutex for a long blocking operation, so items
		 * can be added without worry
		 *
		 * NOTE: this code is duplicated elsewhere; is it better to
		 * have lots of threads, or just one main "worker" thread? */
		queue_mutex_.unlock();
		Services::UpdateAnimeEntry(id);
		queue_mutex_.lock();

		queue_.pop();
	}
	queue_mutex_.unlock();

	emit NeedRefresh();
}

AnimeListPageSortFilter::AnimeListPageSortFilter(QObject *parent) : QSortFilterProxyModel(parent)
{
}

bool AnimeListPageSortFilter::lessThan(const QModelIndex &l, const QModelIndex &r) const
{
	QVariant left = sourceModel()->data(l, sortRole());
	QVariant right = sourceModel()->data(r, sortRole());

	switch (left.userType()) {
		case QMetaType::Int:
		case QMetaType::UInt:
		case QMetaType::LongLong:
		case QMetaType::ULongLong: return left.toInt() < right.toInt();
		case QMetaType::QDate: return left.toDate() < right.toDate();
		case QMetaType::QString:
		default: return QString::compare(left.toString(), right.toString(), Qt::CaseInsensitive) < 0;
	}
}

/* -------------------------------------------------- */

AnimeListPageModel::AnimeListPageModel(QObject *parent, Anime::ListStatus _status) : QAbstractListModel(parent)
{
	status = _status;
	return;
}

int AnimeListPageModel::rowCount(const QModelIndex &parent) const
{
	return list.size();
	(void)(parent);
}

int AnimeListPageModel::columnCount(const QModelIndex &parent) const
{
	return NB_COLUMNS;
	(void)(parent);
}

QVariant AnimeListPageModel::headerData(const int section, const Qt::Orientation orientation, const int role) const
{
	if (role == Qt::DisplayRole) {
		switch (section) {
			case AL_TITLE: return tr("Anime title");
			case AL_PROGRESS: return tr("Progress");
			case AL_EPISODES: return tr("Episodes");
			case AL_TYPE: return tr("Type");
			case AL_SCORE: return tr("Score");
			case AL_SEASON: return tr("Season");
			case AL_STARTED: return tr("Date started");
			case AL_COMPLETED: return tr("Date completed");
			case AL_NOTES: return tr("Notes");
			case AL_AVG_SCORE: return tr("Average score");
			case AL_UPDATED: return tr("Last updated");
			default: return {};
		}
	} else if (role == Qt::TextAlignmentRole) {
		switch (section) {
			case AL_TITLE:
			case AL_NOTES: return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
			case AL_PROGRESS:
			case AL_EPISODES:
			case AL_TYPE:
			case AL_SCORE:
			case AL_AVG_SCORE: return QVariant(Qt::AlignCenter | Qt::AlignVCenter);
			case AL_SEASON:
			case AL_STARTED:
			case AL_COMPLETED:
			case AL_UPDATED: return QVariant(Qt::AlignRight | Qt::AlignVCenter);
			default: return QAbstractListModel::headerData(section, orientation, role);
		}
	}
	return QAbstractListModel::headerData(section, orientation, role);
}

QVariant AnimeListPageModel::data(const QModelIndex &index, int role) const
{
	if (!index.isValid())
		return QVariant();
	switch (role) {
		case Qt::DisplayRole:
			switch (index.column()) {
				case AL_TITLE: return Strings::ToQString(list[index.row()].GetUserPreferredTitle());
				case AL_PROGRESS:
					return QString::number(list[index.row()].GetUserProgress()) + "/" +
					       QString::number(list[index.row()].GetEpisodes());
				case AL_EPISODES: return list[index.row()].GetEpisodes();
				case AL_SCORE: return Strings::ToQString(list[index.row()].GetUserPresentableScore());
				case AL_TYPE: return Strings::ToQString(Translate::ToString(list[index.row()].GetFormat()));
				case AL_SEASON: return Strings::ToQString(Translate::ToLocalString(list[index.row()].GetSeason()));
				case AL_AVG_SCORE: return QString::number(list[index.row()].GetAudienceScore()) + "%";
				case AL_STARTED: return list[index.row()].GetUserDateStarted().GetAsQDate();
				case AL_COMPLETED: return list[index.row()].GetUserDateCompleted().GetAsQDate();
				case AL_UPDATED: {
					if (list[index.row()].GetUserTimeUpdated() == 0)
						return QString("-");
					return Strings::ToQString(Time::GetSecondsAsRelativeString(Time::GetSystemTime() -
					                                                           list[index.row()].GetUserTimeUpdated()));
				}
				case AL_NOTES: return Strings::ToQString(list[index.row()].GetUserNotes());
				default: return "";
			}
			break;
		case Qt::UserRole:
			switch (index.column()) {
				case AL_PROGRESS: return list[index.row()].GetUserProgress();
				case AL_SCORE: return list[index.row()].GetUserScore();
				case AL_TYPE: return static_cast<int>(list[index.row()].GetFormat());
				case AL_SEASON: return list[index.row()].GetStartedDate().GetAsQDate();
				case AL_AVG_SCORE: return list[index.row()].GetAudienceScore();
				case AL_UPDATED: return QVariant::fromValue(list[index.row()].GetUserTimeUpdated());
				default: return data(index, Qt::DisplayRole);
			}
			break;
		case Qt::TextAlignmentRole:
			switch (index.column()) {
				case AL_TITLE:
				case AL_NOTES: return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
				case AL_PROGRESS:
				case AL_EPISODES:
				case AL_TYPE:
				case AL_SCORE:
				case AL_AVG_SCORE: return QVariant(Qt::AlignCenter | Qt::AlignVCenter);
				case AL_SEASON:
				case AL_STARTED:
				case AL_COMPLETED:
				case AL_UPDATED: return QVariant(Qt::AlignRight | Qt::AlignVCenter);
				default: break;
			}
			break;
	}
	return QVariant();
}

Anime::Anime *AnimeListPageModel::GetAnimeFromIndex(QModelIndex index)
{
	return &list.at(index.row());
}

void AnimeListPageModel::RefreshList()
{
	/* equivalent to hasChildren()... */
	if (!rowCount(index(0))) {
		beginInsertRows(QModelIndex(), 0, 0);
		endInsertRows();
	}

	beginResetModel();

	list.clear();

	for (const auto &a : Anime::db.items)
		if (a.second.IsInUserList() && a.second.GetUserStatus() == status)
			list.push_back(a.second);

	endResetModel();
}

/* ----------------------------------------------------------------- */

int AnimeListPage::VisibleColumnsCount() const
{
	int count = 0;

	for (int i = 0, end = tree_view->header()->count(); i < end; i++) {
		if (!tree_view->isColumnHidden(i))
			count++;
	}

	return count;
}

void AnimeListPage::SetColumnDefaults()
{
	tree_view->setColumnHidden(AnimeListPageModel::AL_SEASON, false);
	tree_view->setColumnHidden(AnimeListPageModel::AL_TYPE, false);
	tree_view->setColumnHidden(AnimeListPageModel::AL_UPDATED, false);
	tree_view->setColumnHidden(AnimeListPageModel::AL_PROGRESS, false);
	tree_view->setColumnHidden(AnimeListPageModel::AL_SCORE, false);
	tree_view->setColumnHidden(AnimeListPageModel::AL_TITLE, false);
	tree_view->setColumnHidden(AnimeListPageModel::AL_EPISODES, true);
	tree_view->setColumnHidden(AnimeListPageModel::AL_AVG_SCORE, true);
	tree_view->setColumnHidden(AnimeListPageModel::AL_STARTED, true);
	tree_view->setColumnHidden(AnimeListPageModel::AL_COMPLETED, true);
	tree_view->setColumnHidden(AnimeListPageModel::AL_UPDATED, true);
	tree_view->setColumnHidden(AnimeListPageModel::AL_NOTES, true);
}

void AnimeListPage::UpdateAnime(int id)
{
	/* this ought to just add to the thread's buffer. */
	if (update_entry_thread_.isRunning())
		update_entry_thread_.requestInterruption();

	update_entry_thread_.AddToQueue(id);
	update_entry_thread_.start();
}

void AnimeListPage::RemoveAnime(int id)
{
	Anime::Anime &anime = Anime::db.items[id];
	anime.RemoveFromUserList();
	Refresh();
}

void AnimeListPage::DisplayColumnHeaderMenu()
{
	QMenu *menu = new QMenu(this);
	menu->setAttribute(Qt::WA_DeleteOnClose);
	menu->setTitle(tr("Column visibility"));
	menu->setToolTipsVisible(true);

	for (int i = 0; i < AnimeListPageModel::NB_COLUMNS; i++) {
		if (i == AnimeListPageModel::AL_TITLE)
			continue;
		const auto column_name =
		    sort_models[tab_bar->currentIndex()]->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString();

		QAction *action = menu->addAction(column_name, this, [this, i](const bool checked) {
			if (!checked && (VisibleColumnsCount() <= 1))
				return;

			tree_view->setColumnHidden(i, !checked);

			if (checked && (tree_view->columnWidth(i) <= 5))
				tree_view->resizeColumnToContents(i);

			// FIXME save the state of this
		});

		action->setCheckable(true);
		action->setChecked(!tree_view->isColumnHidden(i));
	}

	menu->addSeparator();
	menu->addAction(tr("Reset to defaults"), this, [this]() {
		for (int i = 0, count = tree_view->header()->count(); i < count; ++i) {
			SetColumnDefaults();
		}
		// SaveSettings();
	});
	menu->popup(QCursor::pos());
}

void AnimeListPage::DisplayListMenu()
{
	QMenu *const menu = new QMenu(this);
	menu->setAttribute(Qt::WA_DeleteOnClose);
	menu->setToolTipsVisible(true);

	AnimeListPageModel *source_model =
	    reinterpret_cast<AnimeListPageModel *>(sort_models[tab_bar->currentIndex()]->sourceModel());
	const QItemSelection selection =
	    sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection());

	std::set<Anime::Anime *> animes;
	for (const auto &index : selection.indexes()) {
		if (!index.isValid())
			continue;

		Anime::Anime *const anime = source_model->GetAnimeFromIndex(index);
		if (!anime)
			continue;

		animes.insert(&Anime::db.items[anime->GetId()]);
	}

	if (animes.size() > 1) {
		// menu in Taiga:
		//
		// Set date started ->
		//   Clear
		//   Set to date started airing
		// Set date completed ->
		//   Clear
		//   Set to date finished airing
		//   Set to last updated
		// Set episode...
		// Set score ->
		//   0
		//   10
		//   ...
		//   100
		// Set status ->
		//   Currently watching
		//   ...
		//   Plan to watch
		// Set notes...
		// ----------------
		// Invert selection
		// ----------------
		// Delete from list... <Del>
	} else if (animes.size() > 0) {
		Anime::Anime *anime = *animes.begin();
		std::optional<std::filesystem::path> path = Library::db.GetAnimeFolder(anime->GetId());

		menu->addAction(tr("Information"), [this, anime] {
			InformationDialog *dialog = new InformationDialog(
			    anime, [this](Anime::Anime *anime) { UpdateAnime(anime->GetId()); }, InformationDialog::PAGE_MAIN_INFO,
			    this);

			dialog->show();
			dialog->raise();
			dialog->activateWindow();
			connect(dialog, &InformationDialog::finished, dialog, &InformationDialog::deleteLater);
		});

		{
			struct {
				bool sep;
				QString name;
				QString format;
			} net[] = {
				/* note: the format should not be percent formatted; Qt does
				 * that automatically */

				{0, tr("AniDB"), "" /* ??? */},
				{0, tr("AniList"), "" /* ??? */},
				{0, tr("Anime News Network"), "https://www.animenewsnetwork.com/search?q=%1"},
				{0, tr("Kitsu"), "" /* ??? */},
				{0, tr("MyAnimeList"), "https://myanimelist.net/search/all?q=%1&cat=all"},
				{0, tr("Reddit"), "https://www.reddit.com/search?q=%1"},
				{0, tr("Wikipedia"), "https://en.wikipedia.org/w/index.php?search=%1&title=Special:Search&ns0=1"},
				{0, tr("YouTube"), "https://www.youtube.com/results?search_query=%1"},
				{1, /* ------------------- */},
				{0, tr("Custom RSS feed"), "" /* ??? */},
				{0, tr("Nyaa.si"), "https://nyaa.si/?f=0&c=0_0&q=%1"},
			};
			size_t i;
			QMenu *msearch = menu->addMenu(tr("Search"));

			for (i = 0; i < (sizeof(net)/sizeof(net[0])); i++) {
				if (net[i].sep) {
					msearch->addSeparator();
				} else {
					if (net[i].format.isEmpty())
						continue;

					msearch->addAction(net[i].name, [anime, net, i] {
						/* I suppose romaji is probably the safest */
						std::optional<std::string> title = anime->GetTitle(Anime::TitleLanguage::Romaji);
						if (!title)
							return; /* wat */

						QString str = net[i].format.arg(Strings::ToQString(title.value()));
						QUrl url(str);

						QDesktopServices::openUrl(url);
					});
				}
			}
		}

		menu->addSeparator();

		menu->addAction(tr("Edit"), [this, anime] {
			InformationDialog *dialog = new InformationDialog(
			    anime, [this](Anime::Anime *anime) { UpdateAnime(anime->GetId()); }, InformationDialog::PAGE_MY_LIST,
			    this);

			dialog->show();
			dialog->raise();
			dialog->activateWindow();
			connect(dialog, &InformationDialog::finished, dialog, &InformationDialog::deleteLater);
		});
		menu->addAction(
		    tr("Delete from list..."), [this, anime] { RemoveAnime(anime->GetId()); },
		    QKeySequence(QKeySequence::Delete));

		menu->addSeparator();

		if (path) {
			menu->addAction(tr("Open folder"), [path] {
				QDesktopServices::openUrl(QUrl::fromLocalFile(Strings::ToQString(path.value().u8string())));
			}, QKeySequence(QKeySequence::Open));
		}

		menu->addAction(tr("Scan available episodes"),
			[this, anime] { Library::db.Refresh(anime->GetId()); },
			QKeySequence(QKeySequence::Refresh));

		if (path) {
			menu->addSeparator();

			{
				QMenu *submenu = menu->addMenu(tr("Play episode"));

				// this submenu actually uses win32 API magic to
				// make a *grid* of episodes (weird!)

				(void)submenu;
			}

			const int progress = anime->GetUserProgress();
			const int episodes = anime->GetEpisodes();

			// I think this is right?
			if (progress > 0) {
				menu->addAction(tr("Play last episode (#%1)").arg(progress), [this, anime, progress] {
					const int id = anime->GetId();

					if (Library::db.items.find(id) == Library::db.items.end() ||
					    Library::db.items[id].find(progress) == Library::db.items[id].end())
						return;

					QDesktopServices::openUrl(
					    QUrl::fromLocalFile(Strings::ToQString(Library::db.items[id][progress].u8string())));
				});
			}

			if (progress < episodes) {
				menu->addAction(
				    tr("Play next episode (#%1)").arg(progress + 1),
				    [this, anime, progress] {
					    const int id = anime->GetId();

					    if (Library::db.items.find(id) == Library::db.items.end() ||
					        Library::db.items[id].find(progress + 1) == Library::db.items[id].end())
						    return;

					    QDesktopServices::openUrl(
					        QUrl::fromLocalFile(Strings::ToQString(Library::db.items[id][progress + 1].u8string())));
				    },
				    QKeySequence(Qt::CTRL | Qt::Key_N));
			}

			menu->addAction(
			    tr("Play random episode"),
			    [this, anime, episodes] {
				    const int id = anime->GetId();

				    std::uniform_int_distribution<int> distrib(1, episodes);
				    const int episode = distrib(session.gen);

				    if (Library::db.items.find(id) == Library::db.items.end() ||
				        Library::db.items[id].find(episode) == Library::db.items[id].end())
					    return;

				    QDesktopServices::openUrl(
				        QUrl::fromLocalFile(Strings::ToQString(Library::db.items[id][episode].u8string())));
			    },
			    QKeySequence(Qt::CTRL | Qt::Key_R));
		}

		menu->popup(QCursor::pos());
	} else {
		// Where are we now?
	}
}

void AnimeListPage::ItemDoubleClicked()
{
	/* throw out any other garbage */
	const QItemSelection selection =
	    sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection());
	if (!selection.indexes().first().isValid()) {
		return;
	}

	AnimeListPageModel *source_model =
	    reinterpret_cast<AnimeListPageModel *>(sort_models[tab_bar->currentIndex()]->sourceModel());

	const QModelIndex index = source_model->index(selection.indexes().first().row());
	Anime::Anime &anime = Anime::db.items[source_model->GetAnimeFromIndex(index)->GetId()];

	InformationDialog *dialog = new InformationDialog(
	    &anime, [this](Anime::Anime *anime) { UpdateAnime(anime->GetId()); }, InformationDialog::PAGE_MAIN_INFO, this);

	dialog->show();
	dialog->raise();
	dialog->activateWindow();
	connect(dialog, &InformationDialog::finished, dialog, &InformationDialog::deleteLater);
}

void AnimeListPage::RefreshList()
{
	for (unsigned int i = 0; i < sort_models.size(); i++)
		reinterpret_cast<AnimeListPageModel *>(sort_models[i]->sourceModel())->RefreshList();
}

void AnimeListPage::RefreshTabs()
{
	for (unsigned int i = 0; i < sort_models.size(); i++)
		tab_bar->setTabText(i, Strings::ToQString(Translate::ToString(Anime::ListStatuses[i])) + " (" +
		                           QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")");
}

void AnimeListPage::Refresh()
{
	RefreshList();
	RefreshTabs();
}

/* -------- QTabWidget replication begin --------- */

void AnimeListPage::InitBasicStyle(QStyleOptionTabWidgetFrame *option) const
{
	if (!option)
		return;

	option->initFrom(this);
	option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, this);
	option->shape = QTabBar::RoundedNorth;
	option->tabBarRect = tab_bar->geometry();
}

void AnimeListPage::InitStyle(QStyleOptionTabWidgetFrame *option) const
{
	if (!option)
		return;

	InitBasicStyle(option);

	QSize t(0, tree_view->frameWidth());
	if (tab_bar->isVisibleTo(this)) {
		t = tab_bar->sizeHint();
		t.setWidth(width());
	}

	option->tabBarSize = t;

	QRect selected_tab_rect = tab_bar->tabRect(tab_bar->currentIndex());
	selected_tab_rect.moveTopLeft(selected_tab_rect.topLeft() + option->tabBarRect.topLeft());
	option->selectedTabRect = selected_tab_rect;

	option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, this);
}

void AnimeListPage::SetupLayout()
{
	QStyleOptionTabWidgetFrame option;
	InitStyle(&option);

	QRect tabRect = style()->subElementRect(QStyle::SE_TabWidgetTabBar, &option, this);
	tabRect.setLeft(tabRect.left() + 1);
	panelRect = style()->subElementRect(QStyle::SE_TabWidgetTabPane, &option, this);
	QRect contentsRect = style()->subElementRect(QStyle::SE_TabWidgetTabContents, &option, this);

	tab_bar->setGeometry(tabRect);
	tree_view->parentWidget()->setGeometry(contentsRect);
}

void AnimeListPage::paintEvent(QPaintEvent *)
{
	QStylePainter p(this);

	QStyleOptionTabWidgetFrame opt;
	InitStyle(&opt);
	opt.rect = panelRect;
	p.drawPrimitive(QStyle::PE_FrameTabWidget, opt);
}

void AnimeListPage::resizeEvent(QResizeEvent *e)
{
	QWidget::resizeEvent(e);
	SetupLayout();
}

void AnimeListPage::showEvent(QShowEvent *)
{
	SetupLayout();
	Refresh();
}

/* --------- QTabWidget replication end ---------- */

AnimeListPage::AnimeListPage(QWidget *parent) : QWidget(parent)
{
	/* Tab bar */
	tab_bar = new QTabBar(this);
	tab_bar->setExpanding(false);
	tab_bar->setDrawBase(false);

	/* Tree view... */
	QWidget *tree_widget = new QWidget(this);
	tree_view = new QTreeView(tree_widget);
	tree_view->setUniformRowHeights(true);
	tree_view->setAllColumnsShowFocus(false);
	tree_view->setAlternatingRowColors(true);
	tree_view->setSortingEnabled(true);
	tree_view->setSelectionMode(QAbstractItemView::ExtendedSelection);
	tree_view->setItemsExpandable(false);
	tree_view->setRootIsDecorated(false);
	tree_view->setContextMenuPolicy(Qt::CustomContextMenu);
	tree_view->setFrameShape(QFrame::NoFrame);

	for (unsigned int i = 0; i < sort_models.size(); i++) {
		tab_bar->addTab(Strings::ToQString(Translate::ToString(Anime::ListStatuses[i])) + " (" +
		                QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")");
		sort_models[i] = new AnimeListPageSortFilter(tree_view);
		sort_models[i]->setSourceModel(new AnimeListPageModel(this, Anime::ListStatuses[i]));
		sort_models[i]->setSortRole(Qt::UserRole);
		sort_models[i]->setSortCaseSensitivity(Qt::CaseInsensitive);
	}

	tree_view->setModel(sort_models[0]);

	/* Set column widths */
	tree_view->setColumnWidth(AnimeListPageModel::AL_TITLE, 300);
	tree_view->setColumnWidth(AnimeListPageModel::AL_PROGRESS, 200);
	tree_view->setColumnWidth(AnimeListPageModel::AL_SCORE, 50);
	tree_view->setColumnWidth(AnimeListPageModel::AL_AVG_SCORE, 55);
	tree_view->setColumnWidth(AnimeListPageModel::AL_TYPE, 65);
	tree_view->setColumnWidth(AnimeListPageModel::AL_SEASON, 95);
	tree_view->setColumnWidth(AnimeListPageModel::AL_STARTED, 90);
	tree_view->setColumnWidth(AnimeListPageModel::AL_COMPLETED, 90);
	tree_view->setColumnWidth(AnimeListPageModel::AL_UPDATED, 100);
	tree_view->setColumnWidth(AnimeListPageModel::AL_NOTES, 100);

	QHBoxLayout *layout = new QHBoxLayout(tree_widget);
	layout->addWidget(tree_view);
	layout->setContentsMargins(0, 0, 0, 0);

	/* Double click stuff */
	connect(tree_view, &QAbstractItemView::doubleClicked, this, &AnimeListPage::ItemDoubleClicked);
	connect(tree_view, &QWidget::customContextMenuRequested, this, &AnimeListPage::DisplayListMenu);

	/* Enter & return keys */
	connect(new QShortcut(Qt::Key_Return, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, this,
	        &AnimeListPage::ItemDoubleClicked);

	connect(new QShortcut(Qt::Key_Enter, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, this,
	        &AnimeListPage::ItemDoubleClicked);

	tree_view->header()->setStretchLastSection(false);
	tree_view->header()->setContextMenuPolicy(Qt::CustomContextMenu);
	connect(tree_view->header(), &QWidget::customContextMenuRequested, this, &AnimeListPage::DisplayColumnHeaderMenu);

	connect(tab_bar, &QTabBar::currentChanged, this, [this](int index) {
		if (sort_models[index])
			tree_view->setModel(sort_models[index]);
	});

	connect(&update_entry_thread_, &AnimeListPageUpdateEntryThread::NeedRefresh, this, &AnimeListPage::Refresh);

	SetColumnDefaults();
	setFocusPolicy(Qt::TabFocus);
	setFocusProxy(tab_bar);
}
