#include "adapters/eft/esp/EspNavigationScanner.h"

#include <cstdint>
#include <string>
#include <utility>

#include "adapters/il2cpp/ManagedRuntime.h"
#include "adapters/il2cpp/Il2CppObjectAccess.h"
#include "adapters/il2cpp/Il2CppString.h"
#include "foundation/diagnostics/OnceLogger.h"
#include "foundation/text/StringUtils.h"
#include "adapters/eft/world/WorldAccess.h"
#include "adapters/eft/localization/EftLocalizationService.h"

namespace ta::adapters::eft::esp
{
namespace detail
{
    void report_path(const char*, const char*)
    {
        ta::foundation::diagnostics::OnceLogger::trace(
            "ESP",
            "compat-navigation",
            "compat.esp.navigation | exfil=Settings.Name localization=raw_key transit=parameters.location,parameters.name");
    }

    bool try_read_string_field(Il2CppObject* object,
                               const char* field_name,
                               std::string& value,
                               const char* log_key,
                               const char* log_message)
    {
        if (!object || !object->klass || !field_name)
            return false;

        const auto result =
            ta::adapters::il2cpp::ManagedRuntime::read_field<Il2CppString*>(object, field_name);
        Il2CppString* managed = result ? result.value() : nullptr;
        if (!managed)
            return false;

        value = ta::adapters::il2cpp::conv_string(managed);
        if (value.empty())
            return false;

        report_path(log_key, log_message);
        return true;
    }

    std::string prettify_identifier(std::string value)
    {
        value = ta::foundation::text::trim_copy(value);
        if (value.empty())
            return {};

        for (char& c : value)
        {
            if (c == '_' || c == '-')
                c = ' ';
        }
        return value;
    }

    std::string localize_key(const std::string& key,
                             const char* log_key,
                             const char* log_message)
    {
        if (key.empty())
            return {};

        const std::string value =
            ta::adapters::eft::localization::EftLocalizationService::localized_text(key);
        if (!value.empty())
            report_path(log_key, log_message);
        return value;
    }

    std::string read_exfil_name(Il2CppObject* settings)
    {
        std::string value;
        try_read_string_field(
            settings,
            "Name",
            value,
            "exfil-name-path-settings-name-field",
            "Exfil name path active: Settings.Name");
        return value;
    }

    std::string localize_exfil_name(const std::string& raw_name)
    {
        const std::string name = ta::foundation::text::trim_copy(raw_name);
        const std::string localized = localize_key(
            name,
            "exfil-localization-path-name",
            "Exfil localization path active: raw name key");
        if (!localized.empty())
            return localized;
        if (!name.empty())
            return prettify_identifier(name);

        ta::foundation::diagnostics::OnceLogger::warn(
            "ESP",
            "exfil-display-path-default",
            "exfil.name unresolved | fallback=Extract");
        return "Extract";
    }

    std::string read_transit_location(Il2CppObject* parameters)
    {
        std::string value;
        try_read_string_field(
            parameters,
            "location",
            value,
            "transit-location-path-field-location",
            "Transit location path active: parameters.location");
        return value;
    }

    std::string read_transit_name(Il2CppObject* parameters)
    {
        std::string value;
        try_read_string_field(
            parameters,
            "name",
            value,
            "transit-name-path-parameters-field-name",
            "Transit name path active: parameters.name");
        return value;
    }

    std::string localize_location_name(const std::string& raw_location)
    {
        const std::string raw = ta::foundation::text::trim_copy(raw_location);
        if (raw.empty())
            return {};

        const std::string localized = localize_key(
            raw,
            "transit-location-localization-raw",
            "Transit location localization path active: raw location key");
        return localized.empty() ? prettify_identifier(raw) : localized;
    }

    std::string localize_transit_point_name(const std::string& raw_name)
    {
        const std::string raw = ta::foundation::text::trim_copy(raw_name);
        if (raw.empty())
            return {};

        const std::string localized = localize_key(
            raw,
            "transit-point-localization-raw",
            "Transit point localization path active: raw name key");
        return localized.empty() ? prettify_identifier(raw) : localized;
    }

    std::string transit_display_name(const std::string& raw_name,
                                     const std::string& raw_location)
    {
        const std::string point = localize_transit_point_name(raw_name);
        const std::string location = localize_location_name(raw_location);

        if (!point.empty() && !location.empty())
        {
            if (ta::foundation::text::case_equal(point, location))
                return location;
            return point + " -> " + location;
        }
        if (!location.empty())
            return location;
        if (!point.empty())
            return point;

        ta::foundation::diagnostics::OnceLogger::warn(
            "ESP",
            "transit-display-path-default",
            "transit.name unresolved | fallback=Transit");
        return "Transit";
    }
}

std::vector<ta::features::esp::EspExfil> EspNavigationScanner::scan_exfils(
    ta::adapters::il2cpp::Il2CppObjectInstance game_world_instance,
    Il2CppObject* main_profile)
{
    std::vector<ta::features::esp::EspExfil> result;
    if (!main_profile)
        return result;

    Il2CppObject* controller_ptr = nullptr;
    if (!game_world_instance.try_get_field(
            "<ExfiltrationController>k__BackingField", controller_ptr) ||
        !controller_ptr)
    {
        return result;
    }

    ta::adapters::il2cpp::Il2CppObjectInstance exfiltration_controller(controller_ptr);
    const auto eligible_points = exfiltration_controller.get_method<
        Il2CppArray*(*)(Il2CppObject*, Il2CppObject*)>("EligiblePoints", 1);
    if (!eligible_points)
        return result;

    Il2CppArray* exfils = eligible_points(
        exfiltration_controller.get_instance(), main_profile);
    if (!exfils)
        return result;

    result.reserve(exfils->max_length);
    for (size_t i = 0; i < exfils->max_length; ++i)
    {
        Il2CppObject* exfil = reinterpret_cast<Il2CppObject**>(&exfils->data)[i];
        if (!exfil)
            continue;

        ta::adapters::il2cpp::Il2CppObjectInstance exfiltration_point(exfil);
        int status = 0;
        Il2CppObject* settings_ptr = nullptr;
        if (!exfiltration_point.try_get_field("_status", status) ||
            !exfiltration_point.try_get_field("Settings", settings_ptr) ||
            !settings_ptr)
        {
            continue;
        }

        const std::string raw_name = detail::read_exfil_name(settings_ptr);
        std::string name = detail::localize_exfil_name(raw_name);

        ta::adapters::unity::vector3 position{};
        if (!ta::adapters::eft::common::get_component_position(exfil, position))
            continue;

        result.push_back({std::move(name), status, position});
    }
    return result;
}

std::vector<ta::features::esp::EspTransit> EspNavigationScanner::scan_transits(
    ta::adapters::il2cpp::Il2CppObjectInstance game_world_instance)
{
    std::vector<ta::features::esp::EspTransit> result;

    Il2CppObject* controller_ptr = nullptr;
    if (!game_world_instance.try_get_field(
            "<TransitController>k__BackingField", controller_ptr) ||
        !controller_ptr)
    {
        return result;
    }

    ta::adapters::il2cpp::Il2CppObjectInstance transit_controller(controller_ptr);
    ta::adapters::unity::dict<int, Il2CppObject*>* transits_by_id = nullptr;
    if (!transit_controller.try_get_field("pointsById", transits_by_id) ||
        !transits_by_id || !transits_by_id->m_p_entries)
    {
        return result;
    }

    result.reserve(static_cast<size_t>(transits_by_id->m_i_count));
    for (int i = 0; i < transits_by_id->m_i_count; ++i)
    {
        Il2CppObject* transit_ptr = transits_by_id->get_value_by_index(i);
        if (!transit_ptr)
            continue;

        ta::adapters::il2cpp::Il2CppObjectInstance transit(transit_ptr);
        Il2CppObject* parameters_ptr = nullptr;
        if (!transit.try_get_field("parameters", parameters_ptr) || !parameters_ptr)
            continue;

        const std::string raw_location = detail::read_transit_location(parameters_ptr);
        const std::string raw_name = detail::read_transit_name(parameters_ptr);
        std::string name = detail::transit_display_name(raw_name, raw_location);

        ta::adapters::unity::vector3 position{};
        if (!ta::adapters::eft::common::get_component_position(transit_ptr, position))
            continue;

        result.push_back({std::move(name), position});
    }
    return result;
}
}
