// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+

#include "SymbolTreeModel.h"

#include <QtWidgets/QApplication>
#include <QtGui/QBrush>
#include <QtGui/QPalette>

#include "common/Assertions.h"

#include "TypeString.h"

SymbolTreeModel::SymbolTreeModel(DebugInterface& cpu, QObject* parent)
	: QAbstractItemModel(parent)
	, m_cpu(cpu)
{
}

QModelIndex SymbolTreeModel::index(int row, int column, const QModelIndex& parent) const
{
	SymbolTreeNode* parent_node = nodeFromIndex(parent);
	if (!parent_node)
		return QModelIndex();

	if (row < 0 || row >= (int)parent_node->children().size())
		return QModelIndex();

	const SymbolTreeNode* child_node = parent_node->children()[row].get();
	if (!child_node)
		return QModelIndex();

	return createIndex(row, column, child_node);
}

QModelIndex SymbolTreeModel::parent(const QModelIndex& index) const
{
	if (!index.isValid())
		return QModelIndex();

	SymbolTreeNode* child_node = nodeFromIndex(index);
	if (!child_node)
		return QModelIndex();

	const SymbolTreeNode* parent_node = child_node->parent();
	if (!parent_node || parent_node == m_root.get())
		return QModelIndex();

	return indexFromNode(*parent_node);
}

int SymbolTreeModel::rowCount(const QModelIndex& parent) const
{
	if (parent.column() > 0)
		return 0;

	SymbolTreeNode* node = nodeFromIndex(parent);
	if (!node)
		return 0;

	return (int)node->children().size();
}

int SymbolTreeModel::columnCount(const QModelIndex& parent) const
{
	return COLUMN_COUNT;
}

bool SymbolTreeModel::hasChildren(const QModelIndex& parent) const
{
	if (!parent.isValid())
		return true;

	SymbolTreeNode* parent_node = nodeFromIndex(parent);
	if (!parent_node)
		return true;

	// If a node doesn't have a type, it can't generate any children, so all the
	// children that will exist must already be there.
	if (!parent_node->type.valid())
		return !parent_node->children().empty();

	bool result = true;
	m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void {
		const ccc::ast::Node* type = parent_node->type.lookup_node(database);
		if (!type)
			return;

		result = nodeHasChildren(*type, database);
	});

	return result;
}

QVariant SymbolTreeModel::data(const QModelIndex& index, int role) const
{
	if (!index.isValid())
		return QVariant();

	SymbolTreeNode* node = nodeFromIndex(index);
	if (!node)
		return QVariant();

	if (role == Qt::ForegroundRole)
	{
		bool active = true;

		// Gray out the names of symbols that have been overwritten in memory.
		if (index.column() == NAME)
			active = node->matchesMemory();

		// Gray out the values of variables that are dead.
		if (index.column() == VALUE && node->liveness().has_value())
			active = *node->liveness();

		QPalette::ColorGroup group = active ? QPalette::Active : QPalette::Disabled;
		return QBrush(QApplication::palette().color(group, QPalette::Text));
	}

	if (role != Qt::DisplayRole)
		return QVariant();

	switch (index.column())
	{
		case NAME:
		{
			return node->name;
		}
		case VALUE:
		{
			if (node->tag != SymbolTreeNode::OBJECT)
				return QVariant();

			return node->display_value();
		}
		case LOCATION:
		{
			return node->location.toString(m_cpu).rightJustified(8);
		}
		case SIZE:
		{
			if (!node->size.has_value())
				return QVariant();

			return QString::number(*node->size);
		}
		case TYPE:
		{
			QVariant result;
			m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void {
				const ccc::ast::Node* type = node->type.lookup_node(database);
				if (!type)
					return;

				result = typeToString(type, database);
			});

			return result;
		}
		case LIVENESS:
		{
			if (!node->liveness().has_value())
				return QVariant();

			return *node->liveness() ? tr("Alive") : tr("Dead");
		}
	}

	return QVariant();
}

bool SymbolTreeModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
	if (!index.isValid())
		return false;

	SymbolTreeNode* node = nodeFromIndex(index);
	if (!node)
		return false;

	bool data_changed = false;
	m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void {
		switch (role)
		{
			case EDIT_ROLE:
				data_changed = node->writeToVM(value, m_cpu, database);
				break;
			case UPDATE_FROM_MEMORY_ROLE:
				data_changed = node->readFromVM(m_cpu, database);
				break;
		}
	});

	if (data_changed)
		emit dataChanged(index.siblingAtColumn(0), index.siblingAtColumn(COLUMN_COUNT - 1));

	return data_changed;
}

void SymbolTreeModel::fetchMore(const QModelIndex& parent)
{
	if (!parent.isValid())
		return;

	SymbolTreeNode* parent_node = nodeFromIndex(parent);
	if (!parent_node || !parent_node->type.valid())
		return;

	if (!parent_node->children().empty())
		return;

	std::vector<std::unique_ptr<SymbolTreeNode>> children;
	m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void {
		const ccc::ast::Node* logical_parent_type = parent_node->type.lookup_node(database);
		if (!logical_parent_type)
			return;

		children = populateChildren(
			parent_node->name, parent_node->location, *logical_parent_type, parent_node->type, m_cpu, database);
	});

	bool insert_children = !children.empty();
	if (insert_children)
		beginInsertRows(parent, 0, children.size() - 1);
	parent_node->setChildren(std::move(children));
	if (insert_children)
		endInsertRows();
}

bool SymbolTreeModel::canFetchMore(const QModelIndex& parent) const
{
	if (!parent.isValid())
		return false;

	SymbolTreeNode* parent_node = nodeFromIndex(parent);
	if (!parent_node || !parent_node->type.valid())
		return false;

	bool result = false;
	m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void {
		const ccc::ast::Node* parent_type = parent_node->type.lookup_node(database);
		if (!parent_type)
			return;

		result = nodeHasChildren(*parent_type, database) && !parent_node->childrenFetched();
	});

	return result;
}

Qt::ItemFlags SymbolTreeModel::flags(const QModelIndex& index) const
{
	if (!index.isValid())
		return Qt::NoItemFlags;

	Qt::ItemFlags flags = QAbstractItemModel::flags(index);

	if (index.column() == LOCATION || index.column() == TYPE || index.column() == VALUE)
		flags |= Qt::ItemIsEditable;

	return flags;
}

QVariant SymbolTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
{
	if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
		return QVariant();

	switch (section)
	{
		case NAME:
			return tr("Name");
		case VALUE:
			return tr("Value");
		case LOCATION:
			return tr("Location");
		case SIZE:
			return tr("Size");
		case TYPE:
			return tr("Type");
		case LIVENESS:
			return tr("Liveness");
	}

	return QVariant();
}

QModelIndex SymbolTreeModel::indexFromNode(const SymbolTreeNode& node) const
{
	int row = 0;
	if (node.parent())
	{
		for (int i = 0; i < (int)node.parent()->children().size(); i++)
			if (node.parent()->children()[i].get() == &node)
				row = i;
	}
	else
		row = 0;

	return createIndex(row, 0, &node);
}

SymbolTreeNode* SymbolTreeModel::nodeFromIndex(const QModelIndex& index) const
{
	if (!index.isValid())
		return m_root.get();

	SymbolTreeNode* node = static_cast<SymbolTreeNode*>(index.internalPointer());
	if (!node)
		return m_root.get();

	return node;
}

void SymbolTreeModel::reset(std::unique_ptr<SymbolTreeNode> new_root)
{
	beginResetModel();
	m_root = std::move(new_root);
	endResetModel();
}

void SymbolTreeModel::resetChildren(QModelIndex index)
{
	pxAssertRel(index.isValid(), "Invalid model index.");

	SymbolTreeNode* node = nodeFromIndex(index);
	if (!node || node->tag != SymbolTreeNode::OBJECT)
		return;

	resetChildrenRecursive(*node);
}

void SymbolTreeModel::resetChildrenRecursive(SymbolTreeNode& node)
{
	for (const std::unique_ptr<SymbolTreeNode>& child : node.children())
		resetChildrenRecursive(*child);

	bool remove_rows = !node.children().empty();
	if (remove_rows)
		beginRemoveRows(indexFromNode(node), 0, node.children().size() - 1);
	node.clearChildren();
	if (remove_rows)
		endRemoveRows();
}

bool SymbolTreeModel::needsReset() const
{
	if (!m_root)
		return true;

	bool needs_reset = false;
	m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) {
		needs_reset = !m_root->anySymbolsValid(database);
	});

	return needs_reset;
}

std::optional<QString> SymbolTreeModel::changeTypeTemporarily(QModelIndex index, std::string_view type_string)
{
	SymbolTreeNode* node = nodeFromIndex(index);
	if (!node)
		return std::nullopt;

	resetChildren(index);

	QString error_message;
	m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void {
		std::unique_ptr<ccc::ast::Node> type = stringToType(type_string, database, error_message);
		if (!error_message.isEmpty())
			return;

		node->temporary_type = std::move(type);
		node->type = ccc::NodeHandle(node->temporary_type.get());
	});

	setData(index, QVariant(), UPDATE_FROM_MEMORY_ROLE);

	return error_message;
}

std::optional<QString> SymbolTreeModel::typeFromModelIndexToString(QModelIndex index)
{
	SymbolTreeNode* node = nodeFromIndex(index);
	if (!node || node->tag != SymbolTreeNode::OBJECT)
		return std::nullopt;

	QString result;
	m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void {
		const ccc::ast::Node* type = node->type.lookup_node(database);
		if (!type)
			return;

		result = typeToString(type, database);
	});

	return result;
}

std::vector<std::unique_ptr<SymbolTreeNode>> SymbolTreeModel::populateChildren(
	const QString& name,
	SymbolTreeLocation location,
	const ccc::ast::Node& logical_type,
	ccc::NodeHandle parent_handle,
	DebugInterface& cpu,
	const ccc::SymbolDatabase& database)
{
	auto [physical_type, symbol] = logical_type.physical_type(database);

	// If we went through a type name, we need to make the node handles for the
	// children point to the new symbol instead of the original one.
	if (symbol)
		parent_handle = ccc::NodeHandle(*symbol, nullptr);

	std::vector<std::unique_ptr<SymbolTreeNode>> children;

	switch (physical_type->descriptor)
	{
		case ccc::ast::ARRAY:
		{
			const ccc::ast::Array& array = physical_type->as<ccc::ast::Array>();

			for (s32 i = 0; i < array.element_count; i++)
			{
				SymbolTreeLocation element_location = location.addOffset(i * array.element_type->size_bytes);
				if (element_location.type == SymbolTreeLocation::NONE)
					continue;

				std::unique_ptr<SymbolTreeNode> element = std::make_unique<SymbolTreeNode>();
				element->name = QString("[%1]").arg(i);
				element->type = parent_handle.handle_for_child(array.element_type.get());
				element->location = element_location;
				children.emplace_back(std::move(element));
			}

			break;
		}
		case ccc::ast::POINTER_OR_REFERENCE:
		{
			const ccc::ast::PointerOrReference& pointer_or_reference = physical_type->as<ccc::ast::PointerOrReference>();

			u32 address = location.read32(cpu);
			if (!cpu.isValidAddress(address))
				break;

			std::unique_ptr<SymbolTreeNode> pointee = std::make_unique<SymbolTreeNode>();
			pointee->name = QString("*%1").arg(name);
			pointee->type = parent_handle.handle_for_child(pointer_or_reference.value_type.get());
			pointee->location = SymbolTreeLocation(SymbolTreeLocation::MEMORY, address);
			children.emplace_back(std::move(pointee));

			break;
		}
		case ccc::ast::STRUCT_OR_UNION:
		{
			const ccc::ast::StructOrUnion& struct_or_union = physical_type->as<ccc::ast::StructOrUnion>();

			std::vector<ccc::ast::StructOrUnion::FlatField> fields;
			struct_or_union.flatten_fields(fields, nullptr, database, true);

			for (const ccc::ast::StructOrUnion::FlatField& field : fields)
			{
				if (field.symbol)
					parent_handle = ccc::NodeHandle(*field.symbol, nullptr);

				SymbolTreeLocation field_location = location.addOffset(field.base_offset + field.node->offset_bytes);
				if (field_location.type == SymbolTreeLocation::NONE)
					continue;

				std::unique_ptr<SymbolTreeNode> child_node = std::make_unique<SymbolTreeNode>();
				if (!field.node->name.empty())
					child_node->name = QString::fromStdString(field.node->name);
				else
					child_node->name = QString("(anonymous %1)").arg(ccc::ast::node_type_to_string(*field.node));
				child_node->type = parent_handle.handle_for_child(field.node);
				child_node->location = field_location;
				children.emplace_back(std::move(child_node));
			}

			break;
		}
		default:
		{
		}
	}

	for (std::unique_ptr<SymbolTreeNode>& child : children)
		child->readFromVM(cpu, database);

	return children;
}

bool SymbolTreeModel::nodeHasChildren(const ccc::ast::Node& logical_type, const ccc::SymbolDatabase& database)
{
	const ccc::ast::Node& type = *logical_type.physical_type(database).first;

	bool result = false;
	switch (type.descriptor)
	{
		case ccc::ast::ARRAY:
		{
			const ccc::ast::Array& array = type.as<ccc::ast::Array>();
			result = array.element_count > 0;
			break;
		}
		case ccc::ast::POINTER_OR_REFERENCE:
		{
			result = true;
			break;
		}
		case ccc::ast::STRUCT_OR_UNION:
		{
			const ccc::ast::StructOrUnion& struct_or_union = type.as<ccc::ast::StructOrUnion>();
			result = !struct_or_union.base_classes.empty() || !struct_or_union.fields.empty();
			break;
		}
		default:
		{
		}
	}

	return result;
}
