#include "pch.hpp"
#include "common.hpp"
#include "core/schema.hpp"
#include "core/memory.hpp"
#include "core/interfaces.hpp"
#include "sdk/interfaces/schema_system.hpp"

struct schema_data_t
{
    fnv1a_t m_hashed_field_name = 0;
    uint32_t m_offset = 0;
};
static std::vector<schema_data_t> m_schema_data = {};

bool schema::setup()
{
    const auto start = std::chrono::high_resolution_clock::now();

    if (!i::m_schema_system)
    {
        LOG_ERROR("m_schema_system is null — interfaces not initialized!");
        return false;
    }
    LOG_INFO("m_schema_system = %p", (void*)i::m_schema_system);

    const auto type_scope = i::m_schema_system->find_type_scope_for_module(CLIENT_DLL);
    if (!type_scope)
    {
        LOG_ERROR("type_scope for '%s' is null", CLIENT_DLL);
        return false;
    }
    LOG_INFO("type_scope = %p", (void*)type_scope);

    const auto hash_classes = type_scope->m_hash_classes();
    LOG_INFO("hash_classes read from offset 0x540");

    const auto table_size = hash_classes.size();
    LOG_INFO("hash_classes table_size = %u", table_size);
    if (table_size == 0)
    {
        LOG_ERROR("hash_classes table is empty — offset 0x540 may be wrong!");
        return false;
    }

    std::unique_ptr<uintptr_t[]> elements = std::make_unique_for_overwrite<uintptr_t[]>(table_size);

    const auto elements_size = hash_classes.get_elements(0, table_size, elements.get());
    LOG_INFO("found '%d' schema classes in module '%s' (out of %u slots)", elements_size, CLIENT_DLL, table_size);

    if (elements_size == 0)
    {
        LOG_ERROR("get_elements returned 0 — hash table iteration may be wrong!");
        return false;
    }

    uint32_t classes_with_fields = 0;
    uint32_t total_fields = 0;

    for (uint32_t idx = 0; idx < elements_size; idx++)
    {
        const auto element = elements[idx];
        if (!element)
            continue;

        const auto class_binding = type_scope->m_hash_classes()[element];
        if (!class_binding)
        {
            LOG_WARNING("class_binding is null for element[%u]=%p", idx, (void*)element);
            continue;
        }

        auto [schema_field_size, schema_field] = class_binding->get_fields();
        if (schema_field_size == 0 || !schema_field)
        {
            LOG_WARNING("class[%u] '%s' has 0 fields or null field ptr (size=%u field=%p)",
                idx, class_binding->m_binary_name().c_str(), schema_field_size, (void*)schema_field);
            continue;
        }

        classes_with_fields++;
        LOG_INFO("class[%u] '%s' has %u fields", idx, class_binding->m_binary_name().c_str(), schema_field_size);

        for (uint32_t f_idx = 0; f_idx < schema_field_size; f_idx++)
        {
            if (!schema_field)
            {
                LOG_WARNING("  field[%u] is null", f_idx);
                continue;
            }

            auto field_name = schema_field->m_name();
            auto field_offset = schema_field->m_single_inheritance_offset();
            auto buff = std::format("{}->{}", class_binding->m_binary_name(), field_name);
            m_schema_data.emplace_back(fnv1a::hash(buff), field_offset);
            total_fields++;

            if (f_idx < 3 || total_fields % 1000 == 0) // log first few then every 1000th
                LOG_INFO("  field[%u] '%s' offset=%u hash=%llu", f_idx, field_name.c_str(), field_offset, fnv1a::hash(buff));

            schema_field = reinterpret_cast<c_schema_class_field_data*>(reinterpret_cast<uintptr_t>(schema_field) + sizeof(c_schema_class_field_data));
        }
    }

    LOG_INFO("classes_with_fields=%u total_fields=%u", classes_with_fields, total_fields);

    if (!m_schema_data.size())
    {
        LOG_ERROR("m_schema_data is empty — no fields enumerated (classes_with_fields=%u)", classes_with_fields);
        return false;
    }

    LOG_INFO("schema initialization completed with %zu entries (took %lldms)",
        m_schema_data.size(),
        std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start).count());
    return true;
}

uint32_t schema::get_offset(const fnv1a_t hashed_field_name)
{
    if (const auto it = std::ranges::find_if(m_schema_data, [hashed_field_name](const schema_data_t& data)
    {
        return data.m_hashed_field_name == hashed_field_name;
    });

    it != m_schema_data.end())
        return it->m_offset;

    LOG_ERROR("failed to find an offset for the field with the hash value '%llu'", hashed_field_name);
    return {};
}
