/* 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 2020-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 "TechnicModel.h"
#include "Application.h"
#include "Json.h"

#include <QIcon>

Technic::ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {}

Technic::ListModel::~ListModel() {}

QVariant Technic::ListModel::data(const QModelIndex& index, int role) const
{
	int pos = index.row();
	if (pos >= modpacks.size() || pos < 0 || !index.isValid()) {
		return QString("INVALID INDEX %1").arg(pos);
	}

	Modpack pack = modpacks.at(pos);
	if (role == Qt::DisplayRole) {
		return pack.name;
	} else if (role == Qt::DecorationRole) {
		if (m_logoMap.contains(pack.logoName)) {
			return (m_logoMap.value(pack.logoName));
		}
		QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
		((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl);
		return icon;
	} else if (role == Qt::UserRole) {
		QVariant v;
		v.setValue(pack);
		return v;
	}
	return QVariant();
}

int Technic::ListModel::columnCount(const QModelIndex&) const
{
	return 1;
}

int Technic::ListModel::rowCount(const QModelIndex&) const
{
	return modpacks.size();
}

void Technic::ListModel::searchWithTerm(const QString& term)
{
	if (currentSearchTerm == term &&
		currentSearchTerm.isNull() == term.isNull()) {
		return;
	}
	currentSearchTerm = term;
	if (jobPtr) {
		jobPtr->abort();
		searchState = ResetRequested;
		return;
	} else {
		beginResetModel();
		modpacks.clear();
		endResetModel();
		searchState = None;
	}
	performSearch();
}

void Technic::ListModel::performSearch()
{
	NetJob* netJob = new NetJob("Technic::Search", APPLICATION->network());
	QString searchUrl = "";
	if (currentSearchTerm.isEmpty()) {
		searchUrl = "https://api.technicpack.net/trending?build=meshmc";
	} else {
		searchUrl =
			QString("https://api.technicpack.net/search?build=meshmc&q=%1")
				.arg(currentSearchTerm);
	}
	netJob->addNetAction(
		Net::Download::makeByteArray(QUrl(searchUrl), &response));
	jobPtr = netJob;
	jobPtr->start();
	QObject::connect(netJob, &NetJob::succeeded, this,
					 &ListModel::searchRequestFinished);
	QObject::connect(netJob, &NetJob::failed, this,
					 &ListModel::searchRequestFailed);
}

void Technic::ListModel::searchRequestFinished()
{
	jobPtr.reset();

	QJsonParseError parse_error;
	QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
	if (parse_error.error != QJsonParseError::NoError) {
		qWarning() << "Error while parsing JSON response from Technic at "
				   << parse_error.offset
				   << " reason: " << parse_error.errorString();
		qWarning() << response;
		return;
	}

	QList<Modpack> newList;
	try {
		auto root = Json::requireObject(doc);
		auto objs = Json::requireArray(root, "modpacks");
		for (auto technicPack : objs) {
			Modpack pack;
			auto technicPackObject = Json::requireObject(technicPack);
			pack.name = Json::requireString(technicPackObject, "name");
			pack.slug = Json::requireString(technicPackObject, "slug");
			if (pack.slug == "vanilla")
				continue;

			auto rawURL =
				Json::ensureString(technicPackObject, "iconUrl", "null");
			if (rawURL == "null") {
				pack.logoUrl = "null";
				pack.logoName = "null";
			} else {
				pack.logoUrl = rawURL;
				pack.logoName = rawURL.section(QLatin1Char('/'), -1)
									.section(QLatin1Char('.'), 0, 0);
			}
			pack.broken = false;
			newList.append(pack);
		}
	} catch (const JSONValidationError& err) {
		qCritical() << "Couldn't parse technic search results:" << err.cause();
		return;
	}
	searchState = Finished;
	beginInsertRows(QModelIndex(), modpacks.size(),
					modpacks.size() + newList.size() - 1);
	modpacks.append(newList);
	endInsertRows();
}

void Technic::ListModel::getLogo(const QString& logo, const QString& logoUrl,
								 Technic::LogoCallback callback)
{
	if (m_logoMap.contains(logo)) {
		callback(
			APPLICATION->metacache()
				->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo))
				->getFullPath());
	} else {
		requestLogo(logo, logoUrl);
	}
}

void Technic::ListModel::searchRequestFailed()
{
	jobPtr.reset();

	if (searchState == ResetRequested) {
		beginResetModel();
		modpacks.clear();
		endResetModel();

		performSearch();
	} else {
		searchState = Finished;
	}
}

void Technic::ListModel::logoLoaded(QString logo, QString out)
{
	m_loadingLogos.removeAll(logo);
	m_logoMap.insert(logo, QIcon(out));
	for (int i = 0; i < modpacks.size(); i++) {
		if (modpacks[i].logoName == logo) {
			emit dataChanged(createIndex(i, 0), createIndex(i, 0),
							 {Qt::DecorationRole});
		}
	}
}

void Technic::ListModel::logoFailed(QString logo)
{
	m_failedLogos.append(logo);
	m_loadingLogos.removeAll(logo);
}

void Technic::ListModel::requestLogo(QString logo, QString url)
{
	if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo) ||
		logo == "null") {
		return;
	}

	MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry(
		"TechnicPacks", QString("logos/%1").arg(logo));
	NetJob* job = new NetJob(QString("Technic Icon Download %1").arg(logo),
							 APPLICATION->network());
	job->addNetAction(Net::Download::makeCached(QUrl(url), entry));

	auto fullPath = entry->getFullPath();

	QObject::connect(job, &NetJob::succeeded, this,
					 [this, logo, fullPath] { logoLoaded(logo, fullPath); });

	QObject::connect(job, &NetJob::failed, this,
					 [this, logo] { logoFailed(logo); });

	job->start();

	m_loadingLogos.append(logo);
}
