/* SPDX-FileCopyrightText: 2026 Project Tick
 * SPDX-FileContributor: Project Tick
 * SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-MeshMC-MMCO-Module-Exception-1.0
 *
 *   MeshMC - A Custom Launcher for Minecraft
 *   Copyright (C) 2026 Project Tick
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version, with the additional permission
 *   described in the MeshMC MMCO Module Exception 1.0.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 *   You should have received a copy of the MeshMC MMCO Module Exception 1.0
 *   along with this program.  If not, see <https://projecttick.org/licenses/>.
 *
 *  This file incorporates work covered by the following copyright and
 *  permission notice:
 *
 * Copyright 2013-2021 MultiMC Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "Application.h"
#include "BuildConfig.h"
#include "plugin/PluginManager.h"
#include "plugin/PluginHooks.h"

#include "MainWindow.h"
#include "ui/MacOSMenuBar.h"
#include "ui/themes/ThemeManager.h"

#include <QtCore/QVariant>
#include <QtCore/QUrl>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>

#include <QtGui/QKeyEvent>

#include <QAction>
#include <QtWidgets/QApplication>
#include <QtWidgets/QButtonGroup>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QStatusBar>
#include <QtWidgets/QToolBar>
#include <QtWidgets/QWidget>
#include <QtWidgets/QMenu>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QInputDialog>
#include <QtWidgets/QLabel>
#include <QtWidgets/QToolButton>
#include <QtWidgets/QWidgetAction>
#include <QtWidgets/QProgressDialog>
#include <QShortcut>

#include <BaseInstance.h>
#include <InstanceList.h>
#include <MMCZip.h>
#include <icons/IconList.h>
#include <java/JavaUtils.h>
#include <java/JavaInstallList.h>
#include <launch/LaunchTask.h>
#include <minecraft/auth/AccountList.h>
#include <SkinUtils.h>
#include <BuildConfig.h>
#include <net/NetJob.h>
#include <net/Download.h>
#include <news/NewsChecker.h>
#include <notifications/NotificationChecker.h>
#include <tools/BaseProfiler.h>

#include <updater/UpdateChecker.h>
#include <DesktopServices.h>
#include "InstanceWindow.h"
#include "InstancePageProvider.h"
#include "JavaCommon.h"
#include "LaunchController.h"

#include "ui/instanceview/InstanceProxyModel.h"
#include "ui/instanceview/InstanceView.h"
#include "ui/instanceview/InstanceDelegate.h"
#include "ui/widgets/LabeledToolButton.h"
#include "ui/dialogs/NewInstanceDialog.h"
#include "ui/dialogs/ProgressDialog.h"
#include "ui/dialogs/AboutDialog.h"
#include "ui/dialogs/MeshMCLogsDialog.h"
#include "ui/dialogs/PluginsDialog.h"
#include "ui/dialogs/UpdateProgressDialog.h"
#include "ui/dialogs/VersionSelectDialog.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/IconPickerDialog.h"
#include "ui/dialogs/CopyInstanceDialog.h"
#include "ui/dialogs/UpdateDialog.h"
#include "ui/dialogs/EditAccountDialog.h"
#include "ui/dialogs/NotificationDialog.h"
#include "ui/dialogs/ExportInstanceDialog.h"

#include "UpdateController.h"
#include "KonamiCode.h"

#include "InstanceImportTask.h"
#include "InstanceCopyTask.h"

#include "MMCTime.h"

namespace
{
	QString profileInUseFilter(const QString& profile, bool used)
	{
		if (used) {
			return QObject::tr("%1 (in use)").arg(profile);
		} else {
			return profile;
		}
	}
} // namespace

// WHY: to hold the pre-translation strings together with the T pointer, so it
// can be retranslated without a lot of ugly code
template <typename T> class Translated
{
  public:
	Translated() {}
	Translated(QWidget* parent)
	{
		m_contained = new T(parent);
	}
	void setTooltipId(const char* tooltip)
	{
		m_tooltip = tooltip;
	}
	void setTextId(const char* text)
	{
		m_text = text;
	}
	operator T*()
	{
		return m_contained;
	}
	T* operator->()
	{
		return m_contained;
	}
	void retranslate()
	{
		if (m_text) {
			QString result;
			result = QApplication::translate("MainWindow", m_text);
			if (result.contains("%1")) {
				result = result.arg(BuildConfig.MESHMC_NAME);
			}
			m_contained->setText(result);
		}
		if (m_tooltip) {
			QString result;
			result = QApplication::translate("MainWindow", m_tooltip);
			if (result.contains("%1")) {
				result = result.arg(BuildConfig.MESHMC_NAME);
			}
			m_contained->setToolTip(result);
		}
	}

  private:
	T* m_contained = nullptr;
	const char* m_text = nullptr;
	const char* m_tooltip = nullptr;
};
using TranslatedAction = Translated<QAction>;
using TranslatedToolButton = Translated<QToolButton>;

class TranslatedToolbar
{
  public:
	TranslatedToolbar() {}
	TranslatedToolbar(QWidget* parent)
	{
		m_contained = new QToolBar(parent);
	}
	void setWindowTitleId(const char* title)
	{
		m_title = title;
	}
	operator QToolBar*()
	{
		return m_contained;
	}
	QToolBar* operator->()
	{
		return m_contained;
	}
	void retranslate()
	{
		if (m_title) {
			m_contained->setWindowTitle(
				QApplication::translate("MainWindow", m_title));
		}
	}

  private:
	QToolBar* m_contained = nullptr;
	const char* m_title = nullptr;
};

class MainWindow::Ui
{
  public:
	TranslatedAction actionAddInstance;
	// TranslatedAction actionRefresh;
	TranslatedAction actionCheckUpdate;
	TranslatedAction actionSettings;
	TranslatedAction actionManageAccounts;
	TranslatedAction actionLaunchInstance;
	TranslatedAction actionRenameInstance;
	TranslatedAction actionChangeInstGroup;
	TranslatedAction actionChangeInstIcon;
	TranslatedAction actionEditInstNotes;
	TranslatedAction actionEditInstance;
	TranslatedAction actionInstanceSettings;
	TranslatedAction actionWorlds;
	TranslatedAction actionMods;
	TranslatedAction actionViewSelectedInstFolder;
	TranslatedAction actionViewSelectedMCFolder;
	TranslatedAction actionViewSelectedModsFolder;
	TranslatedAction actionDeleteInstance;
	TranslatedAction actionConfig_Folder;
	TranslatedAction actionCAT;
	TranslatedAction actionCopyInstance;
	TranslatedAction actionLaunchInstanceOffline;
	TranslatedAction actionScreenshots;
	TranslatedAction actionExportInstance;
	QVector<TranslatedAction*> all_actions;

	LabeledToolButton* renameButton = nullptr;
	LabeledToolButton* changeIconButton = nullptr;

	QMenu* foldersMenu = nullptr;
	TranslatedToolButton foldersMenuButton;
	TranslatedAction actionViewInstanceFolder;
	TranslatedAction actionViewCentralModsFolder;

	QMenu* helpMenu = nullptr;
	TranslatedToolButton helpMenuButton;
	TranslatedAction actionReportBug;
	TranslatedAction actionDISCORD;
	TranslatedAction actionREDDIT;
	TranslatedAction actionPlugins;
	TranslatedAction actionMeshMCLogs;
	TranslatedAction actionAbout;

	QVector<TranslatedToolButton*> all_toolbuttons;

	QWidget* centralWidget = nullptr;
	QHBoxLayout* horizontalLayout = nullptr;
	QStatusBar* statusBar = nullptr;

	TranslatedToolbar mainToolBar;
	TranslatedToolbar instanceToolBar;
	TranslatedToolbar newsToolBar;
	QVector<TranslatedToolbar*> all_toolbars;
	bool m_kill = false;

	void updateLaunchAction()
	{
		if (m_kill) {
			actionLaunchInstance.setTextId(
				QT_TRANSLATE_NOOP("MainWindow", "Kill"));
			actionLaunchInstance.setTooltipId(
				QT_TRANSLATE_NOOP("MainWindow", "Kill the running instance"));
		} else {
			actionLaunchInstance.setTextId(
				QT_TRANSLATE_NOOP("MainWindow", "Launch"));
			actionLaunchInstance.setTooltipId(QT_TRANSLATE_NOOP(
				"MainWindow", "Launch the selected instance."));
		}
		actionLaunchInstance.retranslate();
	}
	void setLaunchAction(bool kill)
	{
		m_kill = kill;
		updateLaunchAction();
	}

	void createMainToolbar(QMainWindow* MainWindow)
	{
		mainToolBar = TranslatedToolbar(MainWindow);
		mainToolBar->setObjectName(QStringLiteral("mainToolBar"));
		mainToolBar->setMovable(false);
		mainToolBar->setAllowedAreas(Qt::TopToolBarArea);
		mainToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
		mainToolBar->setFloatable(false);
		mainToolBar.setWindowTitleId(
			QT_TRANSLATE_NOOP("MainWindow", "Main Toolbar"));

		actionAddInstance = TranslatedAction(MainWindow);
		actionAddInstance->setObjectName(QStringLiteral("actionAddInstance"));
		actionAddInstance->setIcon(APPLICATION->getThemedIcon("new"));
		actionAddInstance.setTextId(
			QT_TRANSLATE_NOOP("MainWindow", "Add Instance"));
		actionAddInstance.setTooltipId(
			QT_TRANSLATE_NOOP("MainWindow", "Add a new instance."));
		all_actions.append(&actionAddInstance);
		mainToolBar->addAction(actionAddInstance);

		mainToolBar->addSeparator();

		foldersMenu = new QMenu(MainWindow);
		foldersMenu->setToolTipsVisible(true);

		actionViewInstanceFolder = TranslatedAction(MainWindow);
		actionViewInstanceFolder->setObjectName(
			QStringLiteral("actionViewInstanceFolder"));
		actionViewInstanceFolder->setIcon(
			APPLICATION->getThemedIcon("viewfolder"));
		actionViewInstanceFolder.setTextId(
			QT_TRANSLATE_NOOP("MainWindow", "View Instance Folder"));
		actionViewInstanceFolder.setTooltipId(QT_TRANSLATE_NOOP(
			"MainWindow", "Open the instance folder in a file browser."));
		all_actions.append(&actionViewInstanceFolder);
		foldersMenu->addAction(actionViewInstanceFolder);

		actionViewCentralModsFolder = TranslatedAction(MainWindow);
		actionViewCentralModsFolder->setObjectName(
			QStringLiteral("actionViewCentralModsFolder"));
		actionViewCentralModsFolder->setIcon(
			APPLICATION->getThemedIcon("centralmods"));
		actionViewCentralModsFolder.setTextId(
			QT_TRANSLATE_NOOP("MainWindow", "View Central Mods Folder"));
		actionViewCentralModsFolder.setTooltipId(QT_TRANSLATE_NOOP(
			"MainWindow", "Open the central mods folder in a file browser."));
		all_actions.append(&actionViewCentralModsFolder);
		foldersMenu->addAction(actionViewCentralModsFolder);

		foldersMenuButton = TranslatedToolButton(MainWindow);
		foldersMenuButton.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Folders"));
		foldersMenuButton.setTooltipId(QT_TRANSLATE_NOOP(
			"MainWindow", "Open one of the folders shared between instances."));
		foldersMenuButton->setMenu(foldersMenu);
		foldersMenuButton->setPopupMode(QToolButton::InstantPopup);
		foldersMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
		foldersMenuButton->setIcon(APPLICATION->getThemedIcon("viewfolder"));
		foldersMenuButton->setFocusPolicy(Qt::NoFocus);
		all_toolbuttons.append(&foldersMenuButton);
		QWidgetAction* foldersButtonAction = new QWidgetAction(MainWindow);
		foldersButtonAction->setDefaultWidget(foldersMenuButton);
		mainToolBar->addAction(foldersButtonAction);

		actionSettings = TranslatedAction(MainWindow);
		actionSettings->setObjectName(QStringLiteral("actionSettings"));
		actionSettings->setIcon(APPLICATION->getThemedIcon("settings"));
		actionSettings->setMenuRole(QAction::PreferencesRole);
		actionSettings.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Settings"));
		actionSettings.setTooltipId(
			QT_TRANSLATE_NOOP("MainWindow", "Change settings."));
		all_actions.append(&actionSettings);
		mainToolBar->addAction(actionSettings);

		helpMenu = new QMenu(MainWindow);
		helpMenu->setToolTipsVisible(true);

		if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) {
			actionReportBug = TranslatedAction(MainWindow);
			actionReportBug->setObjectName(QStringLiteral("actionReportBug"));
			actionReportBug->setIcon(APPLICATION->getThemedIcon("bug"));
			actionReportBug.setTextId(
				QT_TRANSLATE_NOOP("MainWindow", "Report a Bug"));
			actionReportBug.setTooltipId(QT_TRANSLATE_NOOP(
				"MainWindow", "Open the bug tracker to report a bug with %1."));
			all_actions.append(&actionReportBug);
			helpMenu->addAction(actionReportBug);
		}

		if (!BuildConfig.DISCORD_URL.isEmpty()) {
			actionDISCORD = TranslatedAction(MainWindow);
			actionDISCORD->setObjectName(QStringLiteral("actionDISCORD"));
			actionDISCORD->setIcon(APPLICATION->getThemedIcon("discord"));
			actionDISCORD.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Discord"));
			actionDISCORD.setTooltipId(
				QT_TRANSLATE_NOOP("MainWindow", "Open %1 discord voice chat."));
			all_actions.append(&actionDISCORD);
			helpMenu->addAction(actionDISCORD);
		}

		if (!BuildConfig.SUBREDDIT_URL.isEmpty()) {
			actionREDDIT = TranslatedAction(MainWindow);
			actionREDDIT->setObjectName(QStringLiteral("actionREDDIT"));
			actionREDDIT->setIcon(APPLICATION->getThemedIcon("reddit-alien"));
			actionREDDIT.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Reddit"));
			actionREDDIT.setTooltipId(
				QT_TRANSLATE_NOOP("MainWindow", "Open %1 subreddit."));
			all_actions.append(&actionREDDIT);
			helpMenu->addAction(actionREDDIT);
		}

		if (APPLICATION->pluginManager()->moduleCount() >= 1) {
			actionPlugins = TranslatedAction(MainWindow);
			actionPlugins->setObjectName(QStringLiteral("actionPlugins"));
			actionPlugins->setIcon(APPLICATION->getThemedIcon("plugins"));
			actionPlugins.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Plugins"));
			actionPlugins.setTooltipId(QT_TRANSLATE_NOOP(
				"MainWindow", "View and manage MMCO Plugins."));
			all_actions.append(&actionPlugins);
			helpMenu->addAction(actionPlugins);
		}

		actionMeshMCLogs = TranslatedAction(MainWindow);
		actionMeshMCLogs->setObjectName(QStringLiteral("actionMeshMCLogs"));
		actionMeshMCLogs->setIcon(APPLICATION->getThemedIcon("log"));
		actionMeshMCLogs.setTextId(
			QT_TRANSLATE_NOOP("MainWindow", "MeshMC Logs"));
		actionMeshMCLogs.setTooltipId(QT_TRANSLATE_NOOP(
			"MainWindow", "View and manage MeshMC application logs."));
		all_actions.append(&actionMeshMCLogs);
		helpMenu->addAction(actionMeshMCLogs);

		actionAbout = TranslatedAction(MainWindow);
		actionAbout->setObjectName(QStringLiteral("actionAbout"));
		actionAbout->setIcon(APPLICATION->getThemedIcon("about"));
		actionAbout->setMenuRole(QAction::AboutRole);
		actionAbout.setTextId(QT_TRANSLATE_NOOP("MainWindow", "About %1"));
		actionAbout.setTooltipId(
			QT_TRANSLATE_NOOP("MainWindow", "View information about %1."));
		all_actions.append(&actionAbout);
		helpMenu->addAction(actionAbout);

		helpMenuButton = TranslatedToolButton(MainWindow);
		helpMenuButton.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Help"));
		helpMenuButton.setTooltipId(
			QT_TRANSLATE_NOOP("MainWindow", "Get help with %1 or Minecraft."));
		helpMenuButton->setMenu(helpMenu);
		helpMenuButton->setPopupMode(QToolButton::InstantPopup);
		helpMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
		helpMenuButton->setIcon(APPLICATION->getThemedIcon("help"));
		helpMenuButton->setFocusPolicy(Qt::NoFocus);
		all_toolbuttons.append(&helpMenuButton);
		QWidgetAction* helpButtonAction = new QWidgetAction(MainWindow);
		helpButtonAction->setDefaultWidget(helpMenuButton);
		mainToolBar->addAction(helpButtonAction);

		if (BuildConfig.UPDATER_ENABLED &&
			UpdateChecker::isUpdaterSupported()) {
			actionCheckUpdate = TranslatedAction(MainWindow);
			actionCheckUpdate->setObjectName(
				QStringLiteral("actionCheckUpdate"));
			actionCheckUpdate->setIcon(
				APPLICATION->getThemedIcon("checkupdate"));
			actionCheckUpdate.setTextId(
				QT_TRANSLATE_NOOP("MainWindow", "Update"));
			actionCheckUpdate.setTooltipId(QT_TRANSLATE_NOOP(
				"MainWindow", "Check for new updates for %1."));
			all_actions.append(&actionCheckUpdate);
			mainToolBar->addAction(actionCheckUpdate);
		}

		mainToolBar->addSeparator();

		actionCAT = TranslatedAction(MainWindow);
		actionCAT->setObjectName(QStringLiteral("actionCAT"));
		actionCAT->setCheckable(true);
		actionCAT->setIcon(APPLICATION->getThemedIcon("cat"));
		actionCAT.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Meow"));
		actionCAT.setTooltipId(
			QT_TRANSLATE_NOOP("MainWindow", "It's a fluffy kitty :3"));
		actionCAT->setPriority(QAction::LowPriority);
		all_actions.append(&actionCAT);
		mainToolBar->addAction(actionCAT);

		// profile menu and its actions
		actionManageAccounts = TranslatedAction(MainWindow);
		actionManageAccounts->setObjectName(
			QStringLiteral("actionManageAccounts"));
		actionManageAccounts.setTextId(
			QT_TRANSLATE_NOOP("MainWindow", "Manage Accounts"));
		// FIXME: no tooltip!
		actionManageAccounts->setCheckable(false);
		actionManageAccounts->setIcon(APPLICATION->getThemedIcon("accounts"));
		all_actions.append(&actionManageAccounts);

		all_toolbars.append(&mainToolBar);
		MainWindow->addToolBar(Qt::TopToolBarArea, mainToolBar);
	}

	void createStatusBar(QMainWindow* MainWindow)
	{
		statusBar = new QStatusBar(MainWindow);
		statusBar->setObjectName(QStringLiteral("statusBar"));
		MainWindow->setStatusBar(statusBar);
	}

	void createNewsToolbar(QMainWindow* MainWindow)
	{
		newsToolBar = TranslatedToolbar(MainWindow);
		newsToolBar->setObjectName(QStringLiteral("newsToolBar"));
		newsToolBar->setMovable(false);
		newsToolBar->setAllowedAreas(Qt::BottomToolBarArea);
		newsToolBar->setIconSize(QSize(16, 16));
		newsToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
		newsToolBar->setFloatable(false);
		newsToolBar->setWindowTitle(
			QT_TRANSLATE_NOOP("MainWindow", "News Toolbar"));

		all_toolbars.append(&newsToolBar);
		MainWindow->addToolBar(Qt::BottomToolBarArea, newsToolBar);
	}

	void createInstanceToolbar(QMainWindow* MainWindow)
	{
		instanceToolBar = TranslatedToolbar(MainWindow);
		instanceToolBar->setObjectName(QStringLiteral("instanceToolBar"));
		// disabled until we have an instance selected
		instanceToolBar->setEnabled(false);
		instanceToolBar->setAllowedAreas(Qt::LeftToolBarArea |
										 Qt::RightToolBarArea);
		instanceToolBar->setToolButtonStyle(Qt::ToolButtonTextOnly);
		instanceToolBar->setFloatable(false);
		instanceToolBar->setWindowTitle(
			QT_TRANSLATE_NOOP("MainWindow", "Instance Toolbar"));

		// NOTE: not added to toolbar, but used for instance context menu (right
		// click)
		actionChangeInstIcon = TranslatedAction(MainWindow);
		actionChangeInstIcon->setObjectName(
			QStringLiteral("actionChangeInstIcon"));
		actionChangeInstIcon->setIcon(QIcon(":/icons/instances/grass"));
		actionChangeInstIcon->setIconVisibleInMenu(true);
		actionChangeInstIcon.setTextId(
			QT_TRANSLATE_NOOP("MainWindow", "Change Icon"));
		actionChangeInstIcon.setTooltipId(QT_TRANSLATE_NOOP(
			"MainWindow", "Change the selected instance's icon."));
		all_actions.append(&actionChangeInstIcon);

		changeIconButton = new LabeledToolButton(MainWindow);
		changeIconButton->setObjectName(QStringLiteral("changeIconButton"));
		changeIconButton->setIcon(APPLICATION->getThemedIcon("news"));
		changeIconButton->setToolTip(actionChangeInstIcon->toolTip());
		changeIconButton->setSizePolicy(QSizePolicy::Expanding,
										QSizePolicy::Preferred);
		instanceToolBar->addWidget(changeIconButton);

		// NOTE: not added to toolbar, but used for instance context menu (right
		// click)
		actionRenameInstance = TranslatedAction(MainWindow);
		actionRenameInstance->setObjectName(
			QStringLiteral("actionRenameInstance"));
		actionRenameInstance.setTextId(
			QT_TRANSLATE_NOOP("MainWindow", "Rename"));
		actionRenameInstance.setTooltipId(
			QT_TRANSLATE_NOOP("MainWindow", "Rename the selected instance."));
		all_actions.append(&actionRenameInstance);

		// the rename label is inside the rename tool button
		renameButton = new LabeledToolButton(MainWindow);
		renameButton->setObjectName(QStringLiteral("renameButton"));
		renameButton->setToolTip(actionRenameInstance->toolTip());
		renameButton->setSizePolicy(QSizePolicy::Expanding,
									QSizePolicy::Preferred);
		instanceToolBar->addWidget(renameButton);

		instanceToolBar->addSeparator();

		actionLaunchInstance = TranslatedAction(MainWindow);
		actionLaunchInstance->setObjectName(
			QStringLiteral("actionLaunchInstance"));
		all_actions.append(&actionLaunchInstance);
		instanceToolBar->addAction(actionLaunchInstance);

		actionLaunchInstanceOffline = TranslatedAction(MainWindow);
		actionLaunchInstanceOffline->setObjectName(
			QStringLiteral("actionLaunchInstanceOffline"));
		actionLaunchInstanceOffline.setTextId(
			QT_TRANSLATE_NOOP("MainWindow", "Launch Offline"));
		actionLaunchInstanceOffline.setTooltipId(QT_TRANSLATE_NOOP(
			"MainWindow", "Launch the selected instance in offline mode."));
		all_actions.append(&actionLaunchInstanceOffline);
		instanceToolBar->addAction(actionLaunchInstanceOffline);

		instanceToolBar->addSeparator();

		actionEditInstance = TranslatedAction(MainWindow);
		actionEditInstance->setObjectName(QStringLiteral("actionEditInstance"));
		actionEditInstance.setTextId(
			QT_TRANSLATE_NOOP("MainWindow", "Edit Instance"));
		actionEditInstance.setTooltipId(QT_TRANSLATE_NOOP(
			"MainWindow", "Change the instance settings, mods and versions."));
		all_actions.append(&actionEditInstance);
		instanceToolBar->addAction(actionEditInstance);

		actionInstanceSettings = TranslatedAction(MainWindow);
		actionInstanceSettings->setObjectName(
			QStringLiteral("actionInstanceSettings"));
		actionInstanceSettings->setIcon(
			APPLICATION->getThemedIcon("instance-settings"));
		actionInstanceSettings.setTextId(
			QT_TRANSLATE_NOOP("MainWindow", "Instance Settings"));
		actionInstanceSettings.setTooltipId(QT_TRANSLATE_NOOP(
			"MainWindow", "Open the settings for the selected instance."));
		all_actions.append(&actionInstanceSettings);
		instanceToolBar->addAction(actionInstanceSettings);

		actionEditInstNotes = TranslatedAction(MainWindow);
		actionEditInstNotes->setObjectName(
			QStringLiteral("actionEditInstNotes"));
		actionEditInstNotes.setTextId(
			QT_TRANSLATE_NOOP("MainWindow", "Edit Notes"));
		actionEditInstNotes.setTooltipId(QT_TRANSLATE_NOOP(
			"MainWindow", "Edit the notes for the selected instance."));
		all_actions.append(&actionEditInstNotes);
		instanceToolBar->addAction(actionEditInstNotes);

		actionMods = TranslatedAction(MainWindow);
		actionMods->setObjectName(QStringLiteral("actionMods"));
		actionMods.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View Mods"));
		actionMods.setTooltipId(
			QT_TRANSLATE_NOOP("MainWindow", "View the mods of this instance."));
		all_actions.append(&actionMods);
		instanceToolBar->addAction(actionMods);

		actionWorlds = TranslatedAction(MainWindow);
		actionWorlds->setObjectName(QStringLiteral("actionWorlds"));
		actionWorlds.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View Worlds"));
		actionWorlds.setTooltipId(QT_TRANSLATE_NOOP(
			"MainWindow", "View the worlds of this instance."));
		all_actions.append(&actionWorlds);
		instanceToolBar->addAction(actionWorlds);

		actionScreenshots = TranslatedAction(MainWindow);
		actionScreenshots->setObjectName(QStringLiteral("actionScreenshots"));
		actionScreenshots.setTextId(
			QT_TRANSLATE_NOOP("MainWindow", "Manage Screenshots"));
		actionScreenshots.setTooltipId(QT_TRANSLATE_NOOP(
			"MainWindow", "View and upload screenshots for this instance."));
		all_actions.append(&actionScreenshots);
		instanceToolBar->addAction(actionScreenshots);

		actionChangeInstGroup = TranslatedAction(MainWindow);
		actionChangeInstGroup->setObjectName(
			QStringLiteral("actionChangeInstGroup"));
		actionChangeInstGroup.setTextId(
			QT_TRANSLATE_NOOP("MainWindow", "Change Group"));
		actionChangeInstGroup.setTooltipId(QT_TRANSLATE_NOOP(
			"MainWindow", "Change the selected instance's group."));
		all_actions.append(&actionChangeInstGroup);
		instanceToolBar->addAction(actionChangeInstGroup);

		instanceToolBar->addSeparator();

		actionViewSelectedMCFolder = TranslatedAction(MainWindow);
		actionViewSelectedMCFolder->setObjectName(
			QStringLiteral("actionViewSelectedMCFolder"));
		actionViewSelectedMCFolder.setTextId(
			QT_TRANSLATE_NOOP("MainWindow", "Minecraft Folder"));
		actionViewSelectedMCFolder.setTooltipId(QT_TRANSLATE_NOOP(
			"MainWindow", "Open the selected instance's minecraft folder in a "
						  "file browser."));
		all_actions.append(&actionViewSelectedMCFolder);
		instanceToolBar->addAction(actionViewSelectedMCFolder);

		actionViewSelectedModsFolder = TranslatedAction(MainWindow);
		actionViewSelectedModsFolder->setObjectName(
			QStringLiteral("actionViewSelectedModsFolder"));
		actionViewSelectedModsFolder.setTextId(
			QT_TRANSLATE_NOOP("MainWindow", "Mods Folder"));
		actionViewSelectedModsFolder.setTooltipId(QT_TRANSLATE_NOOP(
			"MainWindow",
			"Open the selected instance's mods folder in a file browser."));
		all_actions.append(&actionViewSelectedModsFolder);
		instanceToolBar->addAction(actionViewSelectedModsFolder);

		actionConfig_Folder = TranslatedAction(MainWindow);
		actionConfig_Folder->setObjectName(
			QStringLiteral("actionConfig_Folder"));
		actionConfig_Folder.setTextId(
			QT_TRANSLATE_NOOP("MainWindow", "Config Folder"));
		actionConfig_Folder.setTooltipId(QT_TRANSLATE_NOOP(
			"MainWindow", "Open the instance's config folder."));
		all_actions.append(&actionConfig_Folder);
		instanceToolBar->addAction(actionConfig_Folder);

		actionViewSelectedInstFolder = TranslatedAction(MainWindow);
		actionViewSelectedInstFolder->setObjectName(
			QStringLiteral("actionViewSelectedInstFolder"));
		actionViewSelectedInstFolder.setTextId(
			QT_TRANSLATE_NOOP("MainWindow", "Instance Folder"));
		actionViewSelectedInstFolder.setTooltipId(QT_TRANSLATE_NOOP(
			"MainWindow",
			"Open the selected instance's root folder in a file browser."));
		all_actions.append(&actionViewSelectedInstFolder);
		instanceToolBar->addAction(actionViewSelectedInstFolder);

		instanceToolBar->addSeparator();

		actionExportInstance = TranslatedAction(MainWindow);
		actionExportInstance->setObjectName(
			QStringLiteral("actionExportInstance"));
		actionExportInstance.setTextId(
			QT_TRANSLATE_NOOP("MainWindow", "Export Instance"));
		// FIXME: missing tooltip
		all_actions.append(&actionExportInstance);
		instanceToolBar->addAction(actionExportInstance);

		actionDeleteInstance = TranslatedAction(MainWindow);
		actionDeleteInstance->setObjectName(
			QStringLiteral("actionDeleteInstance"));
		actionDeleteInstance.setTextId(
			QT_TRANSLATE_NOOP("MainWindow", "Delete"));
		actionDeleteInstance.setTooltipId(
			QT_TRANSLATE_NOOP("MainWindow", "Delete the selected instance."));
		all_actions.append(&actionDeleteInstance);
		instanceToolBar->addAction(actionDeleteInstance);

		actionCopyInstance = TranslatedAction(MainWindow);
		actionCopyInstance->setObjectName(QStringLiteral("actionCopyInstance"));
		actionCopyInstance->setIcon(APPLICATION->getThemedIcon("copy"));
		actionCopyInstance.setTextId(
			QT_TRANSLATE_NOOP("MainWindow", "Copy Instance"));
		actionCopyInstance.setTooltipId(
			QT_TRANSLATE_NOOP("MainWindow", "Copy the selected instance."));
		all_actions.append(&actionCopyInstance);
		instanceToolBar->addAction(actionCopyInstance);

		all_toolbars.append(&instanceToolBar);
		MainWindow->addToolBar(Qt::RightToolBarArea, instanceToolBar);
	}

	void setupUi(QMainWindow* MainWindow)
	{
		if (MainWindow->objectName().isEmpty()) {
			MainWindow->setObjectName(QStringLiteral("MainWindow"));
		}
		MainWindow->resize(800, 600);
		MainWindow->setWindowIcon(APPLICATION->getThemedIcon("logo"));
		MainWindow->setWindowTitle(BuildConfig.MESHMC_DISPLAYNAME);
#ifndef QT_NO_ACCESSIBILITY
		MainWindow->setAccessibleName(BuildConfig.MESHMC_NAME);
#endif

		createMainToolbar(MainWindow);

		centralWidget = new QWidget(MainWindow);
		centralWidget->setObjectName(QStringLiteral("centralWidget"));
		horizontalLayout = new QHBoxLayout(centralWidget);
		horizontalLayout->setSpacing(0);
		horizontalLayout->setObjectName(QStringLiteral("horizontalLayout"));
		horizontalLayout->setSizeConstraint(QLayout::SetDefaultConstraint);
		horizontalLayout->setContentsMargins(0, 0, 0, 0);
		MainWindow->setCentralWidget(centralWidget);

		createStatusBar(MainWindow);
		createNewsToolbar(MainWindow);
		createInstanceToolbar(MainWindow);

		retranslateUi(MainWindow);

		QMetaObject::connectSlotsByName(MainWindow);

		// Explicit connections for actions that connectSlotsByName can't
		// auto-connect in Qt6. Guard against null since these actions are only
		// created when the corresponding BuildConfig URLs are non-empty.
		auto mainWin = qobject_cast<class MainWindow*>(MainWindow);
		if (auto* a = actionREDDIT.operator->())
			QObject::connect(a, &QAction::triggered, mainWin,
							 &MainWindow::on_actionREDDIT_triggered);
		if (auto* a = actionDISCORD.operator->())
			QObject::connect(a, &QAction::triggered, mainWin,
							 &MainWindow::on_actionDISCORD_triggered);
		if (auto* a = actionReportBug.operator->())
			QObject::connect(a, &QAction::triggered, mainWin,
							 &MainWindow::on_actionReportBug_triggered);
	} // setupUi

	void retranslateUi(QMainWindow* MainWindow)
	{
		QString winTitle = tr("%1 - Version %2", "MeshMC - Version X")
							   .arg(BuildConfig.MESHMC_DISPLAYNAME,
									BuildConfig.printableVersionString());
		if (!BuildConfig.BUILD_PLATFORM.isEmpty()) {
			winTitle += tr(" on %1", "on platform, as in operating system")
							.arg(BuildConfig.BUILD_PLATFORM);
		}
		MainWindow->setWindowTitle(winTitle);
		// all the actions
		for (auto* item : all_actions) {
			item->retranslate();
		}
		for (auto* item : all_toolbars) {
			item->retranslate();
		}
		for (auto* item : all_toolbuttons) {
			item->retranslate();
		}
		// submenu buttons
		foldersMenuButton->setText(tr("Folders"));
		helpMenuButton->setText(tr("Help"));
	} // retranslateUi
};

MainWindow::MainWindow(QWidget* parent)
	: QMainWindow(parent), ui(new MainWindow::Ui)
{
	ui->setupUi(this);

	// OSX magic.
	setUnifiedTitleAndToolBarOnMac(true);

	// Global shortcuts
	{
		// FIXME: This is kinda weird. and bad. We need some kind of managed
		// shutdown.
		auto q = new QShortcut(QKeySequence::Quit, this);
		connect(q, &QShortcut::activated, qApp, &QApplication::quit);
	}

	// Konami Code
	{
		secretEventFilter = new KonamiCode(this);
		connect(secretEventFilter, &KonamiCode::triggered, this,
				&MainWindow::konamiTriggered);
	}

	// Add the news label to the news toolbar.
	{
		m_newsChecker.reset(
			new NewsChecker(APPLICATION->network(), BuildConfig.NEWS_RSS_URL));
		newsLabel = new QToolButton();
		newsLabel->setIcon(APPLICATION->getThemedIcon("news"));
		newsLabel->setSizePolicy(QSizePolicy::Expanding,
								 QSizePolicy::Preferred);
		newsLabel->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
		newsLabel->setFocusPolicy(Qt::NoFocus);
		// Previously this was `insertWidget(actionMoreNews, newsLabel)`
		// so the headline sat to the LEFT of the "More News…" button.
		// Now that actionMoreNews has been removed, just append the
		// headline at the toolbar's tail — addWidget() handles that
		// without needing a `before` anchor.
		ui->newsToolBar->addWidget(newsLabel);
		QObject::connect(newsLabel, &QAbstractButton::clicked, this,
						 &MainWindow::newsButtonClicked);
		QObject::connect(m_newsChecker.get(), &NewsChecker::newsLoaded, this,
						 &MainWindow::updateNewsLabel);
		updateNewsLabel();
	}

	// Create the instance list widget
	{
		view = new InstanceView(ui->centralWidget);

		view->setSelectionMode(QAbstractItemView::SingleSelection);
		// FIXME: leaks ListViewDelegate
		view->setItemDelegate(new ListViewDelegate(this));
		view->setFrameShape(QFrame::NoFrame);
		// do not show ugly blue border on the mac
		view->setAttribute(Qt::WA_MacShowFocusRect, false);

		view->installEventFilter(this);
		view->setContextMenuPolicy(Qt::CustomContextMenu);
		connect(view, &QWidget::customContextMenuRequested, this,
				&MainWindow::showInstanceContextMenu);
		connect(view, &InstanceView::droppedURLs, this,
				&MainWindow::droppedURLs, Qt::QueuedConnection);

		proxymodel = new InstanceProxyModel(this);
		proxymodel->setSourceModel(APPLICATION->instances().get());
		proxymodel->sort(0);
		connect(proxymodel, &InstanceProxyModel::dataChanged, this,
				&MainWindow::instanceDataChanged);

		view->setModel(proxymodel);
		view->setSourceOfGroupCollapseStatus(
			[](const QString& groupName) -> bool {
				return APPLICATION->instances()->isGroupCollapsed(groupName);
			});
		connect(view, &InstanceView::groupStateChanged,
				APPLICATION->instances().get(),
				&InstanceList::on_GroupStateChanged);
		ui->horizontalLayout->addWidget(view);
	}
	// The cat background
	{
		bool cat_enable = APPLICATION->settings()->get("TheCat").toBool();
		ui->actionCAT->setChecked(cat_enable);
		// NOTE: calling the operator like that is an ugly hack to appease
		// ancient gcc...
		connect(ui->actionCAT.operator->(), &QAction::toggled, this,
				&MainWindow::onCatToggled);
		setCatBackground(cat_enable);
	}
	// start instance when double-clicked
	connect(view, &InstanceView::activated, this,
			&MainWindow::instanceActivated);

	// track the selection -- update the instance toolbar
	connect(view->selectionModel(), &QItemSelectionModel::currentChanged, this,
			&MainWindow::instanceChanged);

	// track icon changes and update the toolbar!
	connect(APPLICATION->icons().get(), &IconList::iconUpdated, this,
			&MainWindow::iconUpdated);

	// model reset -> selection is invalid. All the instance pointers are wrong.
	connect(APPLICATION->instances().get(), &InstanceList::dataIsInvalid, this,
			&MainWindow::selectionBad);

	// handle newly added instances
	connect(APPLICATION->instances().get(),
			&InstanceList::instanceSelectRequest, this,
			&MainWindow::instanceSelectRequest);

	// When the global settings page closes, we want to know about it and update
	// our state
	connect(APPLICATION, &Application::globalSettingsClosed, this,
			&MainWindow::globalSettingsClosed);

	m_statusLeft = new QLabel(tr("No instance selected"), this);
	m_statusCenter = new QLabel(tr("Total playtime: 0s"), this);
	statusBar()->addPermanentWidget(m_statusLeft, 1);
	statusBar()->addPermanentWidget(m_statusCenter, 0);

	// Add "manage accounts" button, right align
	QWidget* spacer = new QWidget();
	spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
	ui->mainToolBar->addWidget(spacer);

	accountMenu = new QMenu(this);

	repopulateAccountsMenu();

	accountMenuButton = new QToolButton(this);
	accountMenuButton->setMenu(accountMenu);
	accountMenuButton->setPopupMode(QToolButton::InstantPopup);
	accountMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
	accountMenuButton->setIcon(APPLICATION->getThemedIcon("noaccount"));

	QWidgetAction* accountMenuButtonAction = new QWidgetAction(this);
	accountMenuButtonAction->setDefaultWidget(accountMenuButton);

	ui->mainToolBar->addAction(accountMenuButtonAction);

	// Update the menu when the active account changes.
	// Shouldn't have to use lambdas here like this, but if I don't, the
	// compiler throws a fit. Template hell sucks...
	connect(APPLICATION->accounts().get(), &AccountList::defaultAccountChanged,
			[this] { defaultAccountChanged(); });
	connect(APPLICATION->accounts().get(), &AccountList::listChanged,
			[this] { repopulateAccountsMenu(); });

	// Show initial account
	defaultAccountChanged();

	// macOS native top bar — composed of the same QActions that drive the
	// toolbars, so triggering a menu item runs the same slot as the toolbar
	// button. No-op on non-Apple builds.
	{
		MacOSMenuBar::Actions menuActions;
		menuActions.addInstance = ui->actionAddInstance.operator->();
		menuActions.launch = ui->actionLaunchInstance.operator->();
		menuActions.launchOffline =
			ui->actionLaunchInstanceOffline.operator->();
		menuActions.editInstance = ui->actionEditInstance.operator->();
		menuActions.instanceSettings = ui->actionInstanceSettings.operator->();
		menuActions.editNotes = ui->actionEditInstNotes.operator->();
		menuActions.viewMods = ui->actionMods.operator->();
		menuActions.viewWorlds = ui->actionWorlds.operator->();
		menuActions.screenshots = ui->actionScreenshots.operator->();
		menuActions.rename = ui->actionRenameInstance.operator->();
		menuActions.changeGroup = ui->actionChangeInstGroup.operator->();
		menuActions.changeIcon = ui->actionChangeInstIcon.operator->();
		menuActions.copyInstance = ui->actionCopyInstance.operator->();
		menuActions.exportInstance = ui->actionExportInstance.operator->();
		menuActions.deleteInstance = ui->actionDeleteInstance.operator->();

		menuActions.viewInstanceFolder =
			ui->actionViewInstanceFolder.operator->();
		menuActions.viewCentralModsFolder =
			ui->actionViewCentralModsFolder.operator->();
		menuActions.viewSelectedMCFolder =
			ui->actionViewSelectedMCFolder.operator->();
		menuActions.viewSelectedModsFolder =
			ui->actionViewSelectedModsFolder.operator->();
		menuActions.viewSelectedConfigFolder =
			ui->actionConfig_Folder.operator->();
		menuActions.viewSelectedInstFolder =
			ui->actionViewSelectedInstFolder.operator->();

		menuActions.toggleCat = ui->actionCAT.operator->();

		menuActions.preferences = ui->actionSettings.operator->();
		menuActions.about = ui->actionAbout.operator->();

		menuActions.manageAccounts = ui->actionManageAccounts.operator->();
		menuActions.accountSubmenu = accountMenu;

		menuActions.reportBug = ui->actionReportBug.operator->();
		menuActions.discord = ui->actionDISCORD.operator->();
		menuActions.reddit = ui->actionREDDIT.operator->();
		menuActions.plugins = ui->actionPlugins.operator->();
		menuActions.viewLogs = ui->actionMeshMCLogs.operator->();
		menuActions.checkUpdate = ui->actionCheckUpdate.operator->();

		MacOSMenuBar::install(this, menuActions);
	}

	// TODO: refresh accounts here?
	// auto accounts = APPLICATION->accounts();

	// load the news
	{
		m_newsChecker->reloadNews();
		updateNewsLabel();
	}

	if (BuildConfig.UPDATER_ENABLED && UpdateChecker::isUpdaterSupported()) {
		bool updatesAllowed = APPLICATION->updatesAreAllowed();
		updatesAllowedChanged(updatesAllowed);

		// NOTE: calling the operator like that is an ugly hack to appease
		// ancient gcc...
		connect(ui->actionCheckUpdate.operator->(), &QAction::triggered, this,
				&MainWindow::checkForUpdates);

		// set up the updater object.
		auto updater = APPLICATION->updateChecker();
		connect(updater.get(), &UpdateChecker::updateAvailable, this,
				&MainWindow::updateAvailable);
		connect(updater.get(), &UpdateChecker::noUpdateFound, this,
				&MainWindow::updateNotAvailable);
		// if automatic update checks are allowed, start one.
		if (APPLICATION->settings()->get("AutoUpdate").toBool() &&
			updatesAllowed) {
			updater->checkForUpdate(false);
		}
	}

	{
		auto checker = new NotificationChecker();
		checker->setNotificationsUrl(QUrl(BuildConfig.NOTIFICATION_URL));
		checker->setApplicationChannel(BuildConfig.VERSION_CHANNEL);
		checker->setApplicationPlatform(BuildConfig.BUILD_PLATFORM);
		checker->setApplicationFullVersion(BuildConfig.FULL_VERSION_STR);
		m_notificationChecker.reset(checker);
		connect(m_notificationChecker.get(),
				&NotificationChecker::notificationCheckFinished, this,
				&MainWindow::notificationsChanged);
		checker->checkForNotifications();
	}

	setSelectedInstanceById(
		APPLICATION->settings()->get("SelectedInstance").toString());

	// removing this looks stupid
	view->setFocus();

	retranslateUi();

	// Notify plugins that the main UI is ready
	if (APPLICATION->pluginManager()) {
		// Add plugin-registered instance toolbar actions
		auto* instanceTB = ui->instanceToolBar.operator->();
		QList<QAction*> tbActions = instanceTB->actions();
		// Find the separator right after actionChangeInstGroup
		QAction* changeGroupAct = ui->actionChangeInstGroup.operator->();
		QAction* insertBefore = nullptr;
		int idx = tbActions.indexOf(changeGroupAct);
		if (idx >= 0 && idx + 1 < tbActions.size() &&
			tbActions[idx + 1]->isSeparator()) {
			insertBefore = tbActions[idx + 1];
		}
		for (const auto& act :
			 APPLICATION->pluginManager()->instanceActions()) {
			auto* qa = new QAction(APPLICATION->getThemedIcon(act.iconName),
								   act.text, this);
			qa->setToolTip(act.tooltip);
			QString pageId = act.pageId;
			connect(qa, &QAction::triggered, this, [this, pageId] {
				APPLICATION->showInstanceWindow(m_selectedInstance, pageId);
			});
			if (insertBefore)
				instanceTB->insertAction(insertBefore, qa);
			else
				instanceTB->addAction(qa);
			m_pluginInstanceActions.append(qa);
		}

		for (const auto& act :
			 APPLICATION->pluginManager()->instanceCallbackActions()) {
			auto* qa = new QAction(APPLICATION->getThemedIcon(act.iconName),
								   act.text, this);
			qa->setToolTip(act.tooltip);
			auto cb = act.callback;
			auto ud = act.userData;
			connect(qa, &QAction::triggered, this, [cb, ud] {
				if (cb)
					cb(ud);
			});
			if (insertBefore)
				instanceTB->insertAction(insertBefore, qa);
			else
				instanceTB->addAction(qa);
			m_pluginInstanceActions.append(qa);
		}

		// Hand plugins direct handles to the long-lived widgets they
		// most commonly want to hook (saves them from scanning
		// qApp->allWidgets() on every load).
		MMCOUiMainReadyPayload mainReady{};
		mainReady.main_window = static_cast<void*>(this);
		mainReady.news_toolbar =
			static_cast<void*>(ui->newsToolBar.operator->());
		mainReady.news_label_button = static_cast<void*>(newsLabel);
		APPLICATION->pluginManager()->dispatchHook(MMCO_HOOK_UI_MAIN_READY,
												   &mainReady);
	}
}

void MainWindow::retranslateUi()
{
	auto accounts = APPLICATION->accounts();
	MinecraftAccountPtr defaultAccount = accounts->defaultAccount();
	if (defaultAccount) {
		auto profileLabel = profileInUseFilter(defaultAccount->profileName(),
											   defaultAccount->isInUse());
		accountMenuButton->setText(profileLabel);
	} else {
		accountMenuButton->setText(tr("Profiles"));
	}

	if (m_selectedInstance) {
		m_statusLeft->setText(m_selectedInstance->getStatusbarDescription());
	} else {
		m_statusLeft->setText(tr("No instance selected"));
	}

	ui->retranslateUi(this);
}

MainWindow::~MainWindow() {}

QMenu* MainWindow::createPopupMenu()
{
	QMenu* filteredMenu = QMainWindow::createPopupMenu();
	filteredMenu->removeAction(ui->mainToolBar->toggleViewAction());
	return filteredMenu;
}

void MainWindow::konamiTriggered()
{
	qDebug() << "Super Secret Mode ACTIVATED!";
}

void MainWindow::showInstanceContextMenu(const QPoint& pos)
{
	QList<QAction*> actions;

	QAction* actionSep = new QAction("", this);
	actionSep->setSeparator(true);

	bool onInstance = view->indexAt(pos).isValid();
	if (onInstance) {
		actions = ui->instanceToolBar->actions();

		// replace the change icon widget with an actual action
		actions.replace(0, ui->actionChangeInstIcon);

		// replace the rename widget with an actual action
		actions.replace(1, ui->actionRenameInstance);

		// add header
		actions.prepend(actionSep);
		QAction* actionVoid = new QAction(m_selectedInstance->name(), this);
		actionVoid->setEnabled(false);
		actions.prepend(actionVoid);
	} else {
		auto group = view->groupNameAt(pos);

		QAction* actionVoid = new QAction(BuildConfig.MESHMC_NAME, this);
		actionVoid->setEnabled(false);

		QAction* actionCreateInstance =
			new QAction(tr("Create instance"), this);
		actionCreateInstance->setToolTip(ui->actionAddInstance->toolTip());
		if (!group.isNull()) {
			QVariantMap data;
			data["group"] = group;
			actionCreateInstance->setData(data);
		}

		connect(actionCreateInstance, &QAction::triggered, this,
				&MainWindow::on_actionAddInstance_triggered);

		actions.prepend(actionSep);
		actions.prepend(actionVoid);
		actions.append(actionCreateInstance);
		if (!group.isNull()) {
			QAction* actionDeleteGroup =
				new QAction(tr("Delete group '%1'").arg(group), this);
			QVariantMap data;
			data["group"] = group;
			actionDeleteGroup->setData(data);
			connect(actionDeleteGroup, &QAction::triggered, this,
					&MainWindow::deleteGroup);
			actions.append(actionDeleteGroup);
		}
	}
	QMenu myMenu;
	myMenu.addActions(actions);
	/*
	if (onInstance)
		myMenu.setEnabled(m_selectedInstance->canLaunch());
	*/

	// Let plugins add items to the context menu
	if (APPLICATION->pluginManager()) {
		QByteArray ctxName = onInstance ? "instance" : "main";
		MMCOMenuEvent menuEvt{};
		menuEvt.context = ctxName.constData();
		menuEvt.menu_handle = &myMenu;
		APPLICATION->pluginManager()->dispatchHook(MMCO_HOOK_UI_CONTEXT_MENU,
												   &menuEvt);
	}

	myMenu.exec(view->mapToGlobal(pos));
}

void MainWindow::updateToolsMenu()
{
	QToolButton* launchButton = dynamic_cast<QToolButton*>(
		ui->instanceToolBar->widgetForAction(ui->actionLaunchInstance));
	QToolButton* launchOfflineButton = dynamic_cast<QToolButton*>(
		ui->instanceToolBar->widgetForAction(ui->actionLaunchInstanceOffline));

	if (!m_selectedInstance || m_selectedInstance->isRunning()) {
		ui->actionLaunchInstance->setMenu(nullptr);
		ui->actionLaunchInstanceOffline->setMenu(nullptr);
		launchButton->setPopupMode(QToolButton::InstantPopup);
		launchOfflineButton->setPopupMode(QToolButton::InstantPopup);
		return;
	}

	QMenu* launchMenu = ui->actionLaunchInstance->menu();
	QMenu* launchOfflineMenu = ui->actionLaunchInstanceOffline->menu();
	launchButton->setPopupMode(QToolButton::MenuButtonPopup);
	launchOfflineButton->setPopupMode(QToolButton::MenuButtonPopup);
	if (launchMenu) {
		launchMenu->clear();
	} else {
		launchMenu = new QMenu(this);
	}
	if (launchOfflineMenu) {
		launchOfflineMenu->clear();
	} else {
		launchOfflineMenu = new QMenu(this);
	}

	QAction* normalLaunch = launchMenu->addAction(tr("Launch"));
	QAction* normalLaunchOffline =
		launchOfflineMenu->addAction(tr("Launch Offline"));
	connect(normalLaunch, &QAction::triggered,
			[this]() { APPLICATION->launch(m_selectedInstance, true); });
	connect(normalLaunchOffline, &QAction::triggered,
			[this]() { APPLICATION->launch(m_selectedInstance, false); });
	QString profilersTitle = tr("Profilers");
	launchMenu->addSeparator()->setText(profilersTitle);
	launchOfflineMenu->addSeparator()->setText(profilersTitle);
	for (auto profiler : APPLICATION->profilers().values()) {
		QAction* profilerAction = launchMenu->addAction(profiler->name());
		QAction* profilerOfflineAction =
			launchOfflineMenu->addAction(profiler->name());
		QString error;
		if (!profiler->check(&error)) {
			profilerAction->setDisabled(true);
			profilerOfflineAction->setDisabled(true);
			QString profilerToolTip = tr("Profiler not setup correctly. Go "
										 "into settings, \"External Tools\".");
			profilerAction->setToolTip(profilerToolTip);
			profilerOfflineAction->setToolTip(profilerToolTip);
		} else {
			connect(profilerAction, &QAction::triggered, [this, profiler]() {
				APPLICATION->launch(m_selectedInstance, true, profiler.get());
			});
			connect(profilerOfflineAction, &QAction::triggered,
					[this, profiler]() {
						APPLICATION->launch(m_selectedInstance, false,
											profiler.get());
					});
		}
	}
	ui->actionLaunchInstance->setMenu(launchMenu);
	ui->actionLaunchInstanceOffline->setMenu(launchOfflineMenu);
}

void MainWindow::repopulateAccountsMenu()
{
	accountMenu->clear();

	auto accounts = APPLICATION->accounts();
	MinecraftAccountPtr defaultAccount = accounts->defaultAccount();

	QString active_profileId = "";
	if (defaultAccount) {
		// this can be called before accountMenuButton exists
		if (accountMenuButton) {
			auto profileLabel = profileInUseFilter(
				defaultAccount->profileName(), defaultAccount->isInUse());
			accountMenuButton->setText(profileLabel);
		}
	}

	if (accounts->count() <= 0) {
		QAction* action = new QAction(tr("No accounts added!"), this);
		action->setEnabled(false);
		accountMenu->addAction(action);
	} else {
		// TODO: Nicer way to iterate?
		for (int i = 0; i < accounts->count(); i++) {
			MinecraftAccountPtr account = accounts->at(i);
			auto profileLabel =
				profileInUseFilter(account->profileName(), account->isInUse());
			QAction* action = new QAction(profileLabel, this);
			action->setData(i);
			action->setCheckable(true);
			if (defaultAccount == account) {
				action->setChecked(true);
			}

			auto face = account->getFace();
			if (!face.isNull()) {
				action->setIcon(face);
			} else {
				action->setIcon(APPLICATION->getThemedIcon("noaccount"));
			}
			accountMenu->addAction(action);
			connect(action, &QAction::triggered, this,
					&MainWindow::changeActiveAccount);
		}
	}

	accountMenu->addSeparator();

	QAction* action = new QAction(tr("No Default Account"), this);
	action->setCheckable(true);
	action->setIcon(APPLICATION->getThemedIcon("noaccount"));
	action->setData(-1);
	if (!defaultAccount) {
		action->setChecked(true);
	}

	accountMenu->addAction(action);
	connect(action, &QAction::triggered, this,
			&MainWindow::changeActiveAccount);

	accountMenu->addSeparator();
	accountMenu->addAction(ui->actionManageAccounts);
}

void MainWindow::updatesAllowedChanged(bool allowed)
{
	if (!BuildConfig.UPDATER_ENABLED || !UpdateChecker::isUpdaterSupported()) {
		return;
	}
	ui->actionCheckUpdate->setEnabled(allowed);
}

/*
 * Assumes the sender is a QAction
 */
void MainWindow::changeActiveAccount()
{
	QAction* sAction = (QAction*)sender();

	// Profile's associated Mojang username
	if (sAction->data().type() != QVariant::Type::Int)
		return;

	QVariant data = sAction->data();
	bool valid = false;
	int index = data.toInt(&valid);
	if (!valid) {
		index = -1;
	}
	auto accounts = APPLICATION->accounts();
	accounts->setDefaultAccount(index == -1 ? nullptr : accounts->at(index));
	defaultAccountChanged();
}

void MainWindow::defaultAccountChanged()
{
	repopulateAccountsMenu();

	MinecraftAccountPtr account = APPLICATION->accounts()->defaultAccount();

	// FIXME: this needs adjustment for MSA
	if (account && account->profileName() != "") {
		auto profileLabel =
			profileInUseFilter(account->profileName(), account->isInUse());
		accountMenuButton->setText(profileLabel);
		auto face = account->getFace();
		if (face.isNull()) {
			accountMenuButton->setIcon(APPLICATION->getThemedIcon("noaccount"));
		} else {
			accountMenuButton->setIcon(face);
		}
		return;
	}

	// Set the icon to the "no account" icon.
	accountMenuButton->setIcon(APPLICATION->getThemedIcon("noaccount"));
	accountMenuButton->setText(tr("Profiles"));
}

bool MainWindow::eventFilter(QObject* obj, QEvent* ev)
{
	if (obj == view) {
		if (ev->type() == QEvent::KeyPress) {
			secretEventFilter->input(ev);
			QKeyEvent* keyEvent = static_cast<QKeyEvent*>(ev);
			switch (keyEvent->key()) {
					/*
				case Qt::Key_Enter:
				case Qt::Key_Return:
					activateInstance(m_selectedInstance);
					return true;
					*/
				case Qt::Key_Delete:
					on_actionDeleteInstance_triggered();
					return true;
				case Qt::Key_F5:
					refreshInstances();
					return true;
				case Qt::Key_F2:
					on_actionRenameInstance_triggered();
					return true;
				default:
					break;
			}
		}
	}
	return QMainWindow::eventFilter(obj, ev);
}

void MainWindow::updateNewsLabel()
{
	if (m_newsChecker->isLoadingNews()) {
		newsLabel->setText(tr("Loading news..."));
		newsLabel->setEnabled(false);
	} else {
		QList<NewsEntryPtr> entries = m_newsChecker->getNewsEntries();
		if (entries.length() > 0) {
			newsLabel->setText(entries[0]->title);
			newsLabel->setEnabled(true);
		} else {
			newsLabel->setText(tr("No news available."));
			newsLabel->setEnabled(false);
		}
	}
}

void MainWindow::updateAvailable(UpdateAvailableStatus status)
{
	if (!APPLICATION->updatesAreAllowed()) {
		updateNotAvailable();
		return;
	}
	UpdateDialog dlg(true, status, this);
	UpdateAction action = (UpdateAction)dlg.exec();
	switch (action) {
		case UPDATE_LATER:
			qDebug() << "Update will be installed later.";
			break;
		case UPDATE_NOW:
			if (!status.downloadUrl.isEmpty()) {
				// Show progress dialog while launching the updater
				auto* progressDlg = new UpdateProgressDialog(this);
				progressDlg->setStatus(tr("Preparing update to version %1...")
										   .arg(status.version));
				progressDlg->appendLog(
					tr("Download URL: %1").arg(status.downloadUrl));
				progressDlg->show();

				APPLICATION->updateIsRunning(true);
				progressDlg->setStatus(tr("Launching updater..."));

				UpdateController controller(this, APPLICATION->root(),
											status.downloadUrl);
				if (controller.startUpdate()) {
					progressDlg->setFinished(
						true, tr("Updater launched. MeshMC will now close."));
					// The updater binary has been launched; quit the main app
					// so the updater can overwrite its files.
					QMetaObject::invokeMethod(qApp, &QCoreApplication::quit,
											  Qt::QueuedConnection);
				} else {
					progressDlg->setFinished(
						false, tr("Failed to launch the updater."));
				}
				APPLICATION->updateIsRunning(false);
			} else {
				CustomMessageBox::selectable(
					this, tr("No Download URL"),
					tr("An update to version %1 is available, but no download "
					   "URL "
					   "was found for your platform (%2).\n"
					   "Please visit the project website to download it "
					   "manually.")
						.arg(status.version, BuildConfig.BUILD_ARTIFACT),
					QMessageBox::Information)
					->show();
			}
			break;
	}
}

void MainWindow::updateNotAvailable()
{
	UpdateDialog dlg(false, {}, this);
	dlg.exec();
}

QList<int> stringToIntList(const QString& string)
{
	QStringList split = string.split(',', Qt::SkipEmptyParts);
	QList<int> out;
	for (int i = 0; i < split.size(); ++i) {
		out.append(split.at(i).toInt());
	}
	return out;
}
QString intListToString(const QList<int>& list)
{
	QStringList slist;
	for (int i = 0; i < list.size(); ++i) {
		slist.append(QString::number(list.at(i)));
	}
	return slist.join(',');
}
void MainWindow::notificationsChanged()
{
	QList<NotificationChecker::NotificationEntry> entries =
		m_notificationChecker->notificationEntries();
	QList<int> shownNotifications = stringToIntList(
		APPLICATION->settings()->get("ShownNotifications").toString());
	for (auto it = entries.begin(); it != entries.end(); ++it) {
		NotificationChecker::NotificationEntry entry = *it;
		if (!shownNotifications.contains(entry.id)) {
			NotificationDialog dialog(entry, this);
			if (dialog.exec() == NotificationDialog::DontShowAgain) {
				shownNotifications.append(entry.id);
			}
		}
	}
	APPLICATION->settings()->set("ShownNotifications",
								 intListToString(shownNotifications));
}

void MainWindow::downloadUpdates(UpdateAvailableStatus status)
{
	// Kept as a stub — actual update installation is now done by the separate
	// meshmc-updater binary launched from updateAvailable().
	Q_UNUSED(status)
}

void MainWindow::onCatToggled(bool state)
{
	setCatBackground(state);
	APPLICATION->settings()->set("TheCat", state);
}

void MainWindow::setCatBackground(bool enabled)
{
	if (enabled) {
		QString catPath = APPLICATION->themeManager()->getCatPack();
		view->setStyleSheet(QString(R"(
InstanceView
{
    background-image: url(%1);
    background-attachment: fixed;
    background-clip: padding;
    background-position: top right;
    background-repeat: none;
    background-color:palette(base);
})")
								.arg(catPath));
	} else {
		view->setStyleSheet(QString());
	}
}

void MainWindow::runModalTask(Task* task)
{
	connect(task, &Task::failed, [this](QString reason) {
		CustomMessageBox::selectable(this, tr("Error"), reason,
									 QMessageBox::Critical)
			->show();
	});
	connect(task, &Task::succeeded, [this, task]() {
		QStringList warnings = task->warnings();
		if (warnings.count()) {
			CustomMessageBox::selectable(
				this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)
				->show();
		}
	});
	ProgressDialog loadDialog(this);
	loadDialog.setSkipButton(true, tr("Abort"));
	loadDialog.execWithTask(task);
}

void MainWindow::instanceFromInstanceTask(InstanceTask* rawTask)
{
	unique_qobject_ptr<Task> task(
		APPLICATION->instances()->wrapInstanceTask(rawTask));
	runModalTask(task.get());
}

void MainWindow::on_actionCopyInstance_triggered()
{
	if (!m_selectedInstance)
		return;

	CopyInstanceDialog copyInstDlg(m_selectedInstance, this);
	if (!copyInstDlg.exec())
		return;

	auto copyTask =
		new InstanceCopyTask(m_selectedInstance, copyInstDlg.shouldCopySaves(),
							 copyInstDlg.shouldKeepPlaytime());
	copyTask->setName(copyInstDlg.instName());
	copyTask->setGroup(copyInstDlg.instGroup());
	copyTask->setIcon(copyInstDlg.iconKey());
	unique_qobject_ptr<Task> task(
		APPLICATION->instances()->wrapInstanceTask(copyTask));
	runModalTask(task.get());
}

void MainWindow::finalizeInstance(InstancePtr inst)
{
	view->updateGeometries();
	setSelectedInstanceById(inst->id());
	if (APPLICATION->accounts()->anyAccountIsValid()) {
		ProgressDialog loadDialog(this);
		auto update = inst->createUpdateTask(Net::Mode::Online);
		connect(update.get(), &Task::failed, [this](QString reason) {
			QString error = QString("Instance load failed: %1").arg(reason);
			CustomMessageBox::selectable(this, tr("Error"), error,
										 QMessageBox::Warning)
				->show();
		});
		if (update) {
			loadDialog.setSkipButton(true, tr("Abort"));
			loadDialog.execWithTask(update.get());
		}
	} else {
		CustomMessageBox::selectable(
			this, tr("Error"),
			tr("MeshMC cannot download Minecraft or update instances unless "
			   "you have at least "
			   "one account added.\nPlease add your Mojang or Minecraft "
			   "account."),
			QMessageBox::Warning)
			->show();
	}
}

void MainWindow::addInstance(QString url)
{
	QString groupName;
	do {
		QObject* obj = sender();
		if (!obj)
			break;
		QAction* action = qobject_cast<QAction*>(obj);
		if (!action)
			break;
		auto map = action->data().toMap();
		if (!map.contains("group"))
			break;
		groupName = map["group"].toString();
	} while (0);

	if (groupName.isEmpty()) {
		groupName = APPLICATION->settings()
						->get("LastUsedGroupForNewInstance")
						.toString();
	}

	NewInstanceDialog newInstDlg(groupName, url, this);
	if (!newInstDlg.exec())
		return;

	APPLICATION->settings()->set("LastUsedGroupForNewInstance",
								 newInstDlg.instGroup());

	InstanceTask* creationTask = newInstDlg.extractTask();
	if (creationTask) {
		instanceFromInstanceTask(creationTask);
	}
}

void MainWindow::on_actionAddInstance_triggered()
{
	addInstance();
}

void MainWindow::droppedURLs(QList<QUrl> urls)
{
	for (auto& url : urls) {
		if (url.isLocalFile()) {
			addInstance(url.toLocalFile());
		} else {
			addInstance(url.toString());
		}
		// Only process one dropped file...
		break;
	}
}

void MainWindow::on_actionREDDIT_triggered()
{
	DesktopServices::openUrl(QUrl(BuildConfig.SUBREDDIT_URL));
}

void MainWindow::on_actionDISCORD_triggered()
{
	DesktopServices::openUrl(QUrl(BuildConfig.DISCORD_URL));
}

void MainWindow::on_actionChangeInstIcon_triggered()
{
	if (!m_selectedInstance)
		return;

	IconPickerDialog dlg(this);
	dlg.execWithSelection(m_selectedInstance->iconKey());
	if (dlg.result() == QDialog::Accepted) {
		m_selectedInstance->setIconKey(dlg.selectedIconKey);
		auto icon = APPLICATION->icons()->getIcon(dlg.selectedIconKey);
		ui->actionChangeInstIcon->setIcon(icon);
		ui->changeIconButton->setIcon(icon);
	}
}

void MainWindow::iconUpdated(QString icon)
{
	if (icon == m_currentInstIcon) {
		auto icon = APPLICATION->icons()->getIcon(m_currentInstIcon);
		ui->actionChangeInstIcon->setIcon(icon);
		ui->changeIconButton->setIcon(icon);
	}
}

void MainWindow::updateInstanceToolIcon(QString new_icon)
{
	m_currentInstIcon = new_icon;
	auto icon = APPLICATION->icons()->getIcon(m_currentInstIcon);
	ui->actionChangeInstIcon->setIcon(icon);
	ui->changeIconButton->setIcon(icon);
}

void MainWindow::setSelectedInstanceById(const QString& id)
{
	if (id.isNull())
		return;
	const QModelIndex index =
		APPLICATION->instances()->getInstanceIndexById(id);
	if (index.isValid()) {
		QModelIndex selectionIndex = proxymodel->mapFromSource(index);
		view->selectionModel()->setCurrentIndex(
			selectionIndex, QItemSelectionModel::ClearAndSelect);
		updateStatusCenter();
	}
}

void MainWindow::on_actionChangeInstGroup_triggered()
{
	if (!m_selectedInstance)
		return;

	bool ok = false;
	InstanceId instId = m_selectedInstance->id();
	QString name(APPLICATION->instances()->getInstanceGroup(instId));
	auto groups = APPLICATION->instances()->getGroups();
	groups.insert(0, "");
	groups.sort(Qt::CaseInsensitive);
	int foo = groups.indexOf(name);

	name = QInputDialog::getItem(this, tr("Group name"),
								 tr("Enter a new group name."), groups, foo,
								 true, &ok);
	name = name.simplified();
	if (ok) {
		APPLICATION->instances()->setInstanceGroup(instId, name);
	}
}

void MainWindow::deleteGroup()
{
	QObject* obj = sender();
	if (!obj)
		return;
	QAction* action = qobject_cast<QAction*>(obj);
	if (!action)
		return;
	auto map = action->data().toMap();
	if (!map.contains("group"))
		return;
	QString groupName = map["group"].toString();
	if (!groupName.isEmpty()) {
		auto reply = QMessageBox::question(
			this, tr("Delete group"),
			tr("Are you sure you want to delete the group %1").arg(groupName),
			QMessageBox::Yes | QMessageBox::No);
		if (reply == QMessageBox::Yes) {
			APPLICATION->instances()->deleteGroup(groupName);
		}
	}
}

void MainWindow::on_actionViewInstanceFolder_triggered()
{
	QString str = APPLICATION->settings()->get("InstanceDir").toString();
	DesktopServices::openDirectory(str);
}

void MainWindow::refreshInstances()
{
	APPLICATION->instances()->loadList();
}

void MainWindow::on_actionViewCentralModsFolder_triggered()
{
	DesktopServices::openDirectory(
		APPLICATION->settings()->get("CentralModsDir").toString(), true);
}

void MainWindow::on_actionConfig_Folder_triggered()
{
	if (m_selectedInstance) {
		QString str = m_selectedInstance->instanceConfigFolder();
		DesktopServices::openDirectory(QDir(str).absolutePath());
	}
}

void MainWindow::checkForUpdates()
{
	if (BuildConfig.UPDATER_ENABLED && UpdateChecker::isUpdaterSupported()) {
		auto updater = APPLICATION->updateChecker();

		// Show the update progress dialog
		auto* progressDlg = new UpdateProgressDialog(this);
		progressDlg->setStatus(tr("Checking for updates..."));
		progressDlg->setAttribute(Qt::WA_DeleteOnClose);

		connect(
			updater.get(), &UpdateChecker::checkFailed, progressDlg,
			[progressDlg](QString reason) {
				progressDlg->setFinished(
					false, QObject::tr("Update check failed: %1").arg(reason));
			},
			Qt::SingleShotConnection);

		connect(
			updater.get(), &UpdateChecker::updateAvailable, progressDlg,
			[progressDlg](UpdateAvailableStatus status) {
				progressDlg->setFinished(
					true, QObject::tr("Update available: version %1")
							  .arg(status.version));
			},
			Qt::SingleShotConnection);

		connect(
			updater.get(), &UpdateChecker::noUpdateFound, progressDlg,
			[progressDlg]() {
				progressDlg->setFinished(
					true, QObject::tr("You are running the latest version."));
			},
			Qt::SingleShotConnection);

		progressDlg->show();
		updater->checkForUpdate(true);
	} else {
		qWarning() << "Updater not set up or not supported on this platform. "
					  "Cannot check for updates.";
	}
}

void MainWindow::on_actionSettings_triggered()
{
	APPLICATION->ShowGlobalSettings(this, "global-settings");
}

void MainWindow::globalSettingsClosed()
{
	// FIXME: quick HACK to make this work. improve, optimize.
	APPLICATION->instances()->loadList();
	proxymodel->invalidate();
	proxymodel->sort(0);
	updateToolsMenu();
	updateStatusCenter();
	update();
}

void MainWindow::on_actionInstanceSettings_triggered()
{
	APPLICATION->showInstanceWindow(m_selectedInstance, "settings");
}

void MainWindow::on_actionEditInstNotes_triggered()
{
	APPLICATION->showInstanceWindow(m_selectedInstance, "notes");
}

void MainWindow::on_actionWorlds_triggered()
{
	APPLICATION->showInstanceWindow(m_selectedInstance, "worlds");
}

void MainWindow::on_actionMods_triggered()
{
	APPLICATION->showInstanceWindow(m_selectedInstance, "mods");
}

void MainWindow::on_actionEditInstance_triggered()
{
	APPLICATION->showInstanceWindow(m_selectedInstance);
}

void MainWindow::on_actionScreenshots_triggered()
{
	APPLICATION->showInstanceWindow(m_selectedInstance, "screenshots");
}

void MainWindow::on_actionManageAccounts_triggered()
{
	APPLICATION->ShowGlobalSettings(this, "accounts");
}

void MainWindow::on_actionReportBug_triggered()
{
	DesktopServices::openUrl(QUrl(BuildConfig.BUG_TRACKER_URL));
}

void MainWindow::newsButtonClicked()
{
	QList<NewsEntryPtr> entries = m_newsChecker->getNewsEntries();
	if (entries.count() > 0) {
		DesktopServices::openUrl(QUrl(entries[0]->link));
	} else {
		DesktopServices::openUrl(
			QUrl("https://projecttick.org/product/meshmc/news"));
	}
}

void MainWindow::on_actionAbout_triggered()
{
	AboutDialog dialog(this);
	dialog.exec();
}

void MainWindow::on_actionPlugins_triggered()
{
	PluginsDialog dialog(this);
	dialog.exec();
}

void MainWindow::on_actionMeshMCLogs_triggered()
{
	MeshMCLogsDialog dialog(this);
	dialog.exec();
}

void MainWindow::on_actionDeleteInstance_triggered()
{
	if (!m_selectedInstance) {
		return;
	}
	auto id = m_selectedInstance->id();
	auto response = CustomMessageBox::selectable(
						this, tr("CAREFUL!"),
						tr("About to delete: %1\nThis is permanent and will "
						   "completely delete the instance.\n\nAre you sure?")
							.arg(m_selectedInstance->name()),
						QMessageBox::Warning,
						QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
						->exec();
	if (response == QMessageBox::Yes) {
		APPLICATION->instances()->deleteInstance(id);
	}
}

void MainWindow::on_actionExportInstance_triggered()
{
	if (m_selectedInstance) {
		ExportInstanceDialog dlg(m_selectedInstance, this);
		dlg.exec();
	}
}

void MainWindow::on_actionRenameInstance_triggered()
{
	if (m_selectedInstance) {
		view->edit(view->currentIndex());
	}
}

void MainWindow::on_actionViewSelectedInstFolder_triggered()
{
	if (m_selectedInstance) {
		QString str = m_selectedInstance->instanceRoot();
		DesktopServices::openDirectory(QDir(str).absolutePath());
	}
}

void MainWindow::on_actionViewSelectedMCFolder_triggered()
{
	if (m_selectedInstance) {
		QString str = m_selectedInstance->gameRoot();
		if (!FS::ensureFilePathExists(str)) {
			// TODO: report error
			return;
		}
		DesktopServices::openDirectory(QDir(str).absolutePath());
	}
}

void MainWindow::on_actionViewSelectedModsFolder_triggered()
{
	if (m_selectedInstance) {
		QString str = m_selectedInstance->modsRoot();
		if (!FS::ensureFilePathExists(str)) {
			// TODO: report error
			return;
		}
		DesktopServices::openDirectory(QDir(str).absolutePath());
	}
}

void MainWindow::closeEvent(QCloseEvent* event)
{
	// Save the window state and geometry.
	APPLICATION->settings()->set("MainWindowState", saveState().toBase64());
	APPLICATION->settings()->set("MainWindowGeometry",
								 saveGeometry().toBase64());
	event->accept();
	emit isClosing();
}

void MainWindow::changeEvent(QEvent* event)
{
	if (event->type() == QEvent::LanguageChange) {
		retranslateUi();
	}
	QMainWindow::changeEvent(event);
}

void MainWindow::instanceActivated(QModelIndex index)
{
	if (!index.isValid())
		return;
	QString id = index.data(InstanceList::InstanceIDRole).toString();
	InstancePtr inst = APPLICATION->instances()->getInstanceById(id);
	if (!inst)
		return;

	activateInstance(inst);
}

void MainWindow::on_actionLaunchInstance_triggered()
{
	if (!m_selectedInstance) {
		return;
	}
	if (m_selectedInstance->isRunning()) {
		APPLICATION->kill(m_selectedInstance);
	} else {
		APPLICATION->launch(m_selectedInstance);
	}
}

void MainWindow::activateInstance(InstancePtr instance)
{
	APPLICATION->launch(instance);
}

void MainWindow::on_actionLaunchInstanceOffline_triggered()
{
	if (m_selectedInstance) {
		APPLICATION->launch(m_selectedInstance, false);
	}
}

void MainWindow::taskEnd()
{
	QObject* sender = QObject::sender();
	if (sender == m_versionLoadTask)
		m_versionLoadTask = NULL;

	sender->deleteLater();
}

void MainWindow::startTask(Task* task)
{
	connect(task, &Task::succeeded, this, &MainWindow::taskEnd);
	connect(task, &Task::failed, this, &MainWindow::taskEnd);
	task->start();
}

void MainWindow::instanceChanged(const QModelIndex& current,
								 const QModelIndex& previous)
{
	if (!current.isValid()) {
		APPLICATION->settings()->set("SelectedInstance", QString());
		selectionBad();
		return;
	}
	QString id = current.data(InstanceList::InstanceIDRole).toString();
	m_selectedInstance = APPLICATION->instances()->getInstanceById(id);
	if (m_selectedInstance) {
		ui->instanceToolBar->setEnabled(true);
		if (m_selectedInstance->isRunning()) {
			ui->actionLaunchInstance->setEnabled(true);
			ui->setLaunchAction(true);
		} else {
			ui->actionLaunchInstance->setEnabled(
				m_selectedInstance->canLaunch());
			ui->setLaunchAction(false);
		}
		ui->actionLaunchInstanceOffline->setEnabled(
			m_selectedInstance->canLaunch());
		ui->actionExportInstance->setEnabled(m_selectedInstance->canExport());
		ui->renameButton->setText(m_selectedInstance->name());
		m_statusLeft->setText(m_selectedInstance->getStatusbarDescription());
		updateStatusCenter();
		updateInstanceToolIcon(m_selectedInstance->iconKey());

		updateToolsMenu();

		APPLICATION->settings()->set("SelectedInstance",
									 m_selectedInstance->id());
	} else {
		ui->instanceToolBar->setEnabled(false);
		APPLICATION->settings()->set("SelectedInstance", QString());
		selectionBad();
		return;
	}
}

void MainWindow::instanceSelectRequest(QString id)
{
	setSelectedInstanceById(id);
}

void MainWindow::instanceDataChanged(const QModelIndex& topLeft,
									 const QModelIndex& bottomRight)
{
	auto current = view->selectionModel()->currentIndex();
	QItemSelection test(topLeft, bottomRight);
	if (test.contains(current)) {
		instanceChanged(current, current);
	}
}

void MainWindow::selectionBad()
{
	// start by reseting everything...
	m_selectedInstance = nullptr;

	statusBar()->clearMessage();
	ui->instanceToolBar->setEnabled(false);
	ui->renameButton->setText(tr("Rename Instance"));
	updateInstanceToolIcon("grass");

	// ...and then see if we can enable the previously selected instance
	setSelectedInstanceById(
		APPLICATION->settings()->get("SelectedInstance").toString());
}

void MainWindow::checkInstancePathForProblems()
{
	QString instanceFolder =
		APPLICATION->settings()->get("InstanceDir").toString();
	if (FS::checkProblemticPathJava(QDir(instanceFolder))) {
		QMessageBox warning(this);
		warning.setText(tr("Your instance folder contains \'!\' and this is "
						   "known to cause Java problems!"));
		warning.setInformativeText(
			tr("You have now two options: <br/>"
			   " - change the instance folder in the settings <br/>"
			   " - move this installation of %1 to a different folder")
				.arg(BuildConfig.MESHMC_NAME));
		warning.setDefaultButton(QMessageBox::Ok);
		warning.exec();
	}
	auto tempFolderText = tr("This is a problem: <br/>"
							 " - MeshMC will likely be deleted without warning "
							 "by the operating system <br/>"
							 " - close MeshMC now and extract it to a real "
							 "location, not a temporary folder");
	QString pathfoldername = QDir(instanceFolder).absolutePath();
	if (pathfoldername.contains("Rar$", Qt::CaseInsensitive)) {
		QMessageBox warning(this);
		warning.setText(tr("Your instance folder contains \'Rar$\' - that "
						   "means you haven't extracted MeshMC archive!"));
		warning.setInformativeText(tempFolderText);
		warning.setDefaultButton(QMessageBox::Ok);
		warning.exec();
	} else if (pathfoldername.startsWith(QDir::tempPath()) ||
			   pathfoldername.contains("/TempState/")) {
		QMessageBox warning(this);
		warning.setText(
			tr("Your instance folder is in a temporary folder: \'%1\'!")
				.arg(QDir::tempPath()));
		warning.setInformativeText(tempFolderText);
		warning.setDefaultButton(QMessageBox::Ok);
		warning.exec();
	}
}

void MainWindow::updateStatusCenter()
{
	m_statusCenter->setVisible(
		APPLICATION->settings()->get("ShowGlobalGameTime").toBool());

	int timePlayed = APPLICATION->instances()->getTotalPlayTime();
	if (timePlayed > 0) {
		m_statusCenter->setText(
			tr("Total playtime: %1").arg(Time::prettifyDuration(timePlayed)));
	}
}
