/* 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 "InstanceDelegate.h"
#include <QPainter>
#include <QTextOption>
#include <QTextLayout>
#include <QApplication>
#include <QtMath>
#include <QDebug>

#include "InstanceView.h"
#include "BaseInstance.h"
#include "InstanceList.h"
#include <xdgicon.h>
#include <QTextEdit>

// Origin: Qt
static void viewItemTextLayout(QTextLayout& textLayout, int lineWidth,
							   qreal& height, qreal& widthUsed)
{
	height = 0;
	widthUsed = 0;
	textLayout.beginLayout();
	QString str = textLayout.text();
	while (true) {
		QTextLine line = textLayout.createLine();
		if (!line.isValid())
			break;
		if (line.textLength() == 0)
			break;
		line.setLineWidth(lineWidth);
		line.setPosition(QPointF(0, height));
		height += line.height();
		widthUsed = qMax(widthUsed, line.naturalTextWidth());
	}
	textLayout.endLayout();
}

ListViewDelegate::ListViewDelegate(QObject* parent)
	: QStyledItemDelegate(parent)
{
}

void drawSelectionRect(QPainter* painter, const QStyleOptionViewItem& option,
					   const QRect& rect)
{
	if ((option.state & QStyle::State_Selected))
		painter->fillRect(rect, option.palette.brush(QPalette::Highlight));
	else {
		QColor backgroundColor = option.palette.color(QPalette::Window);
		backgroundColor.setAlpha(160);
		painter->fillRect(rect, QBrush(backgroundColor));
	}
}

void drawFocusRect(QPainter* painter, const QStyleOptionViewItem& option,
				   const QRect& rect)
{
	if (!(option.state & QStyle::State_HasFocus))
		return;
	QStyleOptionFocusRect opt;
	opt.direction = option.direction;
	opt.fontMetrics = option.fontMetrics;
	opt.palette = option.palette;
	opt.rect = rect;
	// opt.state           = option.state | QStyle::State_KeyboardFocusChange |
	// QStyle::State_Item;
	auto col = option.state & QStyle::State_Selected ? QPalette::Highlight
													 : QPalette::Base;
	opt.backgroundColor = option.palette.color(col);
	// Apparently some widget styles expect this hint to not be set
	painter->setRenderHint(QPainter::Antialiasing, false);

	QStyle* style =
		option.widget ? option.widget->style() : QApplication::style();

	style->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, painter,
						 option.widget);

	painter->setRenderHint(QPainter::Antialiasing);
}

// TODO this can be made a lot prettier
void drawProgressOverlay(QPainter* painter, const QStyleOptionViewItem& option,
						 const int value, const int maximum)
{
	if (maximum == 0 || value == maximum) {
		return;
	}

	painter->save();

	qreal percent = (qreal)value / (qreal)maximum;
	QColor color = option.palette.color(QPalette::Dark);
	color.setAlphaF(0.70f);
	painter->setBrush(color);
	painter->setPen(QPen(QBrush(), 0));
	painter->drawPie(option.rect, 90 * 16, -percent * 360 * 16);

	painter->restore();
}

void drawBadges(QPainter* painter, const QStyleOptionViewItem& option,
				BaseInstance* instance, QIcon::Mode mode, QIcon::State state)
{
	QList<QString> pixmaps;
	if (instance->isRunning()) {
		pixmaps.append("status-running");
	} else if (instance->hasCrashed() || instance->hasVersionBroken()) {
		pixmaps.append("status-bad");
	}
	if (instance->hasUpdateAvailable()) {
		pixmaps.append("checkupdate");
	}

	static const int itemSide = 24;
	static const int spacing = 1;
	const int itemsPerRow =
		qMax(1, qFloor(double(option.rect.width() + spacing) /
					   double(itemSide + spacing)));
	const int rows = qCeil((double)pixmaps.size() / (double)itemsPerRow);
	QListIterator<QString> it(pixmaps);
	painter->translate(option.rect.topLeft());
	for (int y = 0; y < rows; ++y) {
		for (int x = 0; x < itemsPerRow; ++x) {
			if (!it.hasNext()) {
				return;
			}
			// FIXME: inject this.
			auto icon = XdgIcon::fromTheme(it.next());
			// opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state);
			const QPixmap pixmap;
			// itemSide
			QRect badgeRect(option.rect.width() - x * itemSide +
								qMax(x - 1, 0) * spacing - itemSide,
							y * itemSide + qMax(y - 1, 0) * spacing, itemSide,
							itemSide);
			icon.paint(painter, badgeRect, Qt::AlignCenter, mode, state);
		}
	}
	painter->translate(-option.rect.topLeft());
}

static QSize viewItemTextSize(const QStyleOptionViewItem* option)
{
	QStyle* style =
		option->widget ? option->widget->style() : QApplication::style();
	QTextOption textOption;
	textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
	QTextLayout textLayout;
	textLayout.setTextOption(textOption);
	textLayout.setFont(option->font);
	textLayout.setText(option->text);
	const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin,
											  option, option->widget) +
						   1;
	QRect bounds(0, 0, 100 - 2 * textMargin, 600);
	qreal height = 0, widthUsed = 0;
	viewItemTextLayout(textLayout, bounds.width(), height, widthUsed);
	const QSize size(qCeil(widthUsed), qCeil(height));
	return QSize(size.width() + 2 * textMargin, size.height());
}

void ListViewDelegate::paint(QPainter* painter,
							 const QStyleOptionViewItem& option,
							 const QModelIndex& index) const
{
	QStyleOptionViewItem opt = option;
	initStyleOption(&opt, index);
	painter->save();
	painter->setClipRect(opt.rect);

	opt.features |= QStyleOptionViewItem::WrapText;
	opt.text = index.data().toString();
	opt.textElideMode = Qt::ElideRight;
	opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter;

	QStyle* style = opt.widget ? opt.widget->style() : QApplication::style();

	// const int iconSize =  style->pixelMetric(QStyle::PM_IconViewIconSize);
	const int iconSize = 48;
	QRect iconbox = opt.rect;
	const int textMargin =
		style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, opt.widget) + 1;
	QRect textRect = opt.rect;
	QRect textHighlightRect = textRect;
	// clip the decoration on top, remove width padding
	textRect.adjust(textMargin, iconSize + textMargin + 5, -textMargin, 0);

	textHighlightRect.adjust(0, iconSize + 5, 0, 0);

	// draw background
	{
		// FIXME: unused
		// QSize textSize = viewItemTextSize ( &opt );
		drawSelectionRect(painter, opt, textHighlightRect);
		/*
		QPalette::ColorGroup cg;
		QStyleOptionViewItem opt2(opt);

		if ((opt.widget && opt.widget->isEnabled()) || (opt.state &
		QStyle::State_Enabled))
		{
			if (!(opt.state & QStyle::State_Active))
				cg = QPalette::Inactive;
			else
				cg = QPalette::Normal;
		}
		else
		{
			cg = QPalette::Disabled;
		}
		*/
		/*
		opt2.palette.setCurrentColorGroup(cg);

		// fill in background, if any


		if (opt.backgroundBrush.style() != Qt::NoBrush)
		{
			QPointF oldBO = painter->brushOrigin();
			painter->setBrushOrigin(opt.rect.topLeft());
			painter->fillRect(opt.rect, opt.backgroundBrush);
			painter->setBrushOrigin(oldBO);
		}

		drawSelectionRect(painter, opt2, textHighlightRect);
		*/

		/*
		if (opt.showDecorationSelected)
		{
			drawSelectionRect(painter, opt2, opt.rect);
			drawFocusRect(painter, opt2, opt.rect);
			// painter->fillRect ( opt.rect, opt.palette.brush ( cg,
		QPalette::Highlight ) );
		}
		else
		{

			// if ( opt.state & QStyle::State_Selected )
			{
				// QRect textRect = subElementRect (
		QStyle::SE_ItemViewItemText,  opt,
				// opt.widget );
				// painter->fillRect ( textHighlightRect, opt.palette.brush (
		cg,
				// QPalette::Highlight ) );
				drawSelectionRect(painter, opt2, textHighlightRect);
				drawFocusRect(painter, opt2, textHighlightRect);
			}
		}
		*/
	}

	// icon mode and state, also used for badges
	QIcon::Mode mode = QIcon::Normal;
	if (!(opt.state & QStyle::State_Enabled))
		mode = QIcon::Disabled;
	else if (opt.state & QStyle::State_Selected)
		mode = QIcon::Selected;
	QIcon::State state =
		opt.state & QStyle::State_Open ? QIcon::On : QIcon::Off;

	// draw the icon
	{
		iconbox.setHeight(iconSize);
		opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state);
	}
	// set the text colors
	QPalette::ColorGroup cg = opt.state & QStyle::State_Enabled
								  ? QPalette::Normal
								  : QPalette::Disabled;
	if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active))
		cg = QPalette::Inactive;
	if (opt.state & QStyle::State_Selected) {
		painter->setPen(opt.palette.color(cg, QPalette::HighlightedText));
	} else {
		painter->setPen(opt.palette.color(cg, QPalette::Text));
	}

	// draw the text
	QTextOption textOption;
	textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
	textOption.setTextDirection(opt.direction);
	textOption.setAlignment(
		QStyle::visualAlignment(opt.direction, opt.displayAlignment));
	QTextLayout textLayout;
	textLayout.setTextOption(textOption);
	textLayout.setFont(opt.font);
	textLayout.setText(opt.text);

	qreal width, height;
	viewItemTextLayout(textLayout, textRect.width(), height, width);

	const int lineCount = textLayout.lineCount();

	const QRect layoutRect =
		QStyle::alignedRect(opt.direction, opt.displayAlignment,
							QSize(textRect.width(), int(height)), textRect);
	const QPointF position = layoutRect.topLeft();
	for (int i = 0; i < lineCount; ++i) {
		const QTextLine line = textLayout.lineAt(i);
		line.draw(painter, position);
	}

	// FIXME: this really has no business of being here. Make generic.
	auto instance = (BaseInstance*)index.data(InstanceList::InstancePointerRole)
						.value<void*>();
	if (instance) {
		drawBadges(painter, opt, instance, mode, state);
	}

	drawProgressOverlay(
		painter, opt, index.data(InstanceViewRoles::ProgressValueRole).toInt(),
		index.data(InstanceViewRoles::ProgressMaximumRole).toInt());

	painter->restore();
}

QSize ListViewDelegate::sizeHint(const QStyleOptionViewItem& option,
								 const QModelIndex& index) const
{
	QStyleOptionViewItem opt = option;
	initStyleOption(&opt, index);
	opt.features |= QStyleOptionViewItem::WrapText;
	opt.text = index.data().toString();
	opt.textElideMode = Qt::ElideRight;
	opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter;

	QStyle* style = opt.widget ? opt.widget->style() : QApplication::style();
	const int textMargin =
		style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, opt.widget) +
		1;
	int height = 48 + textMargin * 2 + 5; // TODO: turn constants into variables
	QSize szz = viewItemTextSize(&opt);
	height += szz.height();
	// FIXME: maybe the icon items could scale and keep proportions?
	QSize sz(100, height);
	return sz;
}

class NoReturnTextEdit : public QTextEdit
{
	Q_OBJECT
  public:
	explicit NoReturnTextEdit(QWidget* parent) : QTextEdit(parent)
	{
		setTextInteractionFlags(Qt::TextEditorInteraction);
		setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
		setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
	}
	bool event(QEvent* event) override
	{
		auto eventType = event->type();
		if (eventType == QEvent::KeyPress || eventType == QEvent::KeyRelease) {
			QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
			auto key = keyEvent->key();
			if (key == Qt::Key_Return || key == Qt::Key_Enter) {
				emit editingDone();
				return true;
			}
			if (key == Qt::Key_Tab) {
				return true;
			}
		}
		return QTextEdit::event(event);
	}
  signals:
	void editingDone();
};

void ListViewDelegate::updateEditorGeometry(QWidget* editor,
											const QStyleOptionViewItem& option,
											const QModelIndex& index) const
{
	const int iconSize = 48;
	QRect textRect = option.rect;
	// QStyle *style = option.widget ? option.widget->style() :
	// QApplication::style();
	textRect.adjust(0, iconSize + 5, 0, 0);
	editor->setGeometry(textRect);
}

void ListViewDelegate::setEditorData(QWidget* editor,
									 const QModelIndex& index) const
{
	auto text = index.data(Qt::EditRole).toString();
	QTextEdit* realeditor = qobject_cast<NoReturnTextEdit*>(editor);
	realeditor->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
	realeditor->append(text);
	realeditor->selectAll();
	realeditor->document()->clearUndoRedoStacks();
}

void ListViewDelegate::setModelData(QWidget* editor, QAbstractItemModel* model,
									const QModelIndex& index) const
{
	QTextEdit* realeditor = qobject_cast<NoReturnTextEdit*>(editor);
	QString text = realeditor->toPlainText();
	text.replace(QChar('\n'), QChar(' '));
	text = text.trimmed();
	if (text.size() != 0) {
		model->setData(index, text);
	}
}

QWidget* ListViewDelegate::createEditor(QWidget* parent,
										const QStyleOptionViewItem& option,
										const QModelIndex& index) const
{
	auto editor = new NoReturnTextEdit(parent);
	connect(editor, &NoReturnTextEdit::editingDone, this,
			&ListViewDelegate::editingDone);
	return editor;
}

void ListViewDelegate::editingDone()
{
	NoReturnTextEdit* editor = qobject_cast<NoReturnTextEdit*>(sender());
	emit commitData(editor);
	emit closeEditor(editor);
}

#include "InstanceDelegate.moc"
