// -----------------------------------------------------------------------------
// schema.cpp
// -----------------------------------------------------------------------------
// Implementation of the schema system access described in schema.h.
//
// Memory layout (mirrors valthrun's cs2/src/schema/schema.rs):
//
//   CSchemaSystem                      (instance, from a byte signature)
//     +0x190  scopes: CUtlVector<CSchemaSystemTypeScope*>  { size u32, ptr }
//
//   CSchemaSystemTypeScope
//     +0x008  scope_name: char[0x100]  (inline)
//     +0x470  type_declared_class: UtlRBTree<IdHashEntry>
//     +0x4A0  type_declared_enum:  UtlRBTree<IdHashEntry>
//
//   UtlRBTree<T>                       { entry_count u16, capacity u16, ptr }
//   UtlRBTreeNode<T> = T + 0x08, where IdHashEntry = { u64 id, ptr value }
//     (node: 4x i16 + IdHashEntry => value pointer at node + 0x10)
//
//   CSchemaTypeDeclaredClass
//     +0x020  declaration: CSchemaClassBinding*
//
//   CSchemaClassBinding
//     +0x008  name: const char*
//     +0x024  field_size: u16
//     +0x030  fields: CSchemaClassField**
//
//   CSchemaClassField
//     +0x000  name: const char*
//     +0x010  offset: u32
// -----------------------------------------------------------------------------

#include "schema.h"

#include "../util/logger.h"

namespace core {

namespace {

// Sanity limits so a corrupt pointer cannot cause an endless loop.
constexpr uint32_t kMaxScopes = 0x20;
constexpr uint32_t kMaxClasses = 0x8000;
constexpr uint32_t kMaxFields = 0x4000;

// Field array interpretations tried when dumping a class. Since the Aug
// 2026 CS2 update the fields array can be a pointer array (valthrun layout)
// or a contiguous struct array (a2x layout); the dump probes both and picks
// the one that yields plausible (name, offset) pairs.
constexpr uint32_t kFieldStridePointer = 0x08;  // array of CSchemaClassField*
constexpr uint32_t kFieldStrideStruct = 0x20;   // array of CSchemaClassField

// Reads an inline fixed-size C string (e.g. the scope name).
std::string ReadInlineString(const ProcessMemory& memory, uintptr_t address, size_t maxLength)
{
	return memory.ReadString(address, maxLength);
}

// True when the string looks like a real schema field name: non-empty,
// printable and short enough to be a member name.
bool IsPlausibleFieldName(const std::string& name)
{
	if (name.empty() || name.size() > 128) {
		return false;
	}
	for (const unsigned char c : name) {
		if (c < 0x20 || c == 0x7F) {
			return false;
		}
	}
	return true;
}

// Reads one field with the given array stride and validates it. Returns
// true when the (name, offset) pair looks like a real schema field.
bool TryReadField(const ProcessMemory& memory, uintptr_t fields, uint32_t stride,
	uint32_t index, std::string& outName, uint32_t& outOffset)
{
	const uintptr_t field = fields + static_cast<uintptr_t>(index) * stride;
	const uintptr_t namePtr = memory.Read<uintptr_t>(field + 0x00);
	if (namePtr == 0) {
		return false;
	}

	outName = memory.ReadString(namePtr, 128);
	if (!IsPlausibleFieldName(outName)) {
		return false;
	}

	outOffset = memory.Read<uint32_t>(field + 0x10);
	return outOffset < 0x100000;
}

} // namespace

bool ReadBuildInfo(const ProcessMemory& memory, uintptr_t engineBase,
	uintptr_t buildInfoOffset, std::string& outDatetime)
{
	const uintptr_t buildInfo = engineBase + buildInfoOffset;

	const uintptr_t revisionPtr = memory.Read<uintptr_t>(buildInfo + 0x00);
	const uintptr_t datePtr = memory.Read<uintptr_t>(buildInfo + 0x08);
	const uintptr_t timePtr = memory.Read<uintptr_t>(buildInfo + 0x10);
	if (revisionPtr == 0 || datePtr == 0) {
		return false;
	}

	const std::string revision = memory.ReadString(revisionPtr, 64);
	const std::string date = memory.ReadString(datePtr, 64);
	const std::string time = (timePtr != 0) ? memory.ReadString(timePtr, 64) : "";

	if (revision.empty() || date.empty()) {
		return false;
	}

	outDatetime = date + " " + time;
	return true;
}

bool DumpSchemaOffsets(const ProcessMemory& memory, uintptr_t schemaSystemAddress,
	std::vector<SchemaOffsetEntry>& outOffsets)
{
	outOffsets.clear();

	const uint32_t scopeCount = memory.Read<uint32_t>(schemaSystemAddress + 0x190);
	const uintptr_t scopesData = memory.Read<uintptr_t>(schemaSystemAddress + 0x198);
	if (scopeCount == 0 || scopeCount > kMaxScopes || scopesData == 0) {
		util::LogError("schema dump failed: invalid scopes vector");
		return false;
	}

	for (uint32_t s = 0; s < scopeCount; s++) {
		const uintptr_t scope = memory.Read<uintptr_t>(scopesData + static_cast<uintptr_t>(s) * 8);
		if (scope == 0) {
			continue;
		}

		const std::string scopeName = ReadInlineString(memory, scope + 0x08, 0x100);
		if (scopeName != "client.dll" && scopeName != "engine2.dll" &&
			scopeName != "!GlobalTypes") {
			continue;
		}

		const uint16_t treeCount = memory.Read<uint16_t>(scope + 0x470);
		const uintptr_t treeData = memory.Read<uintptr_t>(scope + 0x478);
		if (treeCount == 0 || treeData == 0) {
			continue;
		}

		const uint32_t limit = std::min<uint32_t>(treeCount, kMaxClasses);
		for (uint32_t n = 0; n < limit; n++) {
			const uintptr_t node = treeData + static_cast<uintptr_t>(n) * 0x18;
			const uintptr_t declaredType = memory.Read<uintptr_t>(node + 0x10);
			if (declaredType == 0) {
				continue;
			}

			const uintptr_t binding = memory.Read<uintptr_t>(declaredType + 0x20);
			if (binding == 0) {
				continue;
			}

			const uintptr_t namePtr = memory.Read<uintptr_t>(binding + 0x08);
			const uint16_t fieldCount = memory.Read<uint16_t>(binding + 0x24);
			const uintptr_t fields = memory.Read<uintptr_t>(binding + 0x30);
			if (namePtr == 0 || fields == 0) {
				continue;
			}

			const std::string className = memory.ReadString(namePtr, 128);
			if (className.empty()) {
				continue;
			}

			const uint32_t fieldLimit = std::min<uint32_t>(fieldCount, kMaxFields);
			uint32_t pointerHits = 0;
			uint32_t structHits = 0;
			for (uint32_t f = 0; f < fieldLimit; f++) {
				std::string name;
				uint32_t offset = 0;
				if (TryReadField(memory, fields, kFieldStridePointer, f, name, offset)) {
					++pointerHits;
				}
				if (TryReadField(memory, fields, kFieldStrideStruct, f, name, offset)) {
					++structHits;
				}
			}

			const bool useStructs = structHits > pointerHits;
			const uint32_t stride = useStructs ? kFieldStrideStruct : kFieldStridePointer;
			for (uint32_t f = 0; f < fieldLimit; f++) {
				std::string member;
				uint32_t offset = 0;
				if (!TryReadField(memory, fields, stride, f, member, offset)) {
					continue;
				}
				outOffsets.push_back(SchemaOffsetEntry{
					scopeName,
					className,
					std::move(member),
					offset,
				});
			}
		}
	}

	return !outOffsets.empty();
}

} // namespace core
