#include "presentation/overlays/esp/EspOverlayBuilder.h"

#include <cmath>
#include <string>
#include <utility>

#include "presentation/runtime/Viewport.h"
#include "foundation/math/SpatialMath.h"
#include "foundation/math/ScreenProjection.h"
#include "application/messaging/PresentationState.h"
#include "presentation/localization/UiLocalizationService.h"
#include "presentation/imgui/ImGuiConversions.h"

namespace ta::presentation::overlays::esp
{
namespace
{
    std::string exfil_status_string(const int status)
    {
        switch (status)
        {
        case 7: return ta::presentation::localization::tr("Hidden");
        case 6: return ta::presentation::localization::tr("Manual Activation");
        case 5: return ta::presentation::localization::tr("Pending");
        case 4: return ta::presentation::localization::tr("Open");
        case 3: return ta::presentation::localization::tr("Countdown");
        case 2: return ta::presentation::localization::tr("Requirements");
        case 1: return ta::presentation::localization::tr("Not Present");
        default: return ta::presentation::localization::tr("Unknown");
        }
    }

    std::string distance_label(const float distance, const std::string& name)
    {
        return "[" + std::to_string(static_cast<int>(distance)) + "m] " + name;
    }

    bool append_projected_label(EspOverlayDrawData& data,
                                const ta::foundation::math::CameraFrame& camera,
                                const float viewport_width,
                                const float viewport_height,
                                const ta::foundation::math::Vector3& world_pos,
                                const ImVec4& color,
                                std::string text,
                                const bool use_hud_font,
                                const float font_size = 0.0f,
                                const float y_offset = 0.0f)
    {
        ta::foundation::math::Vector3 screen_pos{};
        if (!ta::foundation::math::project_visible(
                camera,
                world_pos,
                viewport_width,
                viewport_height,
                screen_pos))
        {
            return false;
        }

        data.labels.push_back({
            ImVec2(screen_pos.x, screen_pos.y + y_offset),
            color,
            std::move(text),
            use_hud_font,
            font_size
        });
        return true;
    }

    template <typename Entry, typename TextBuilder>
    void append_point(EspOverlayDrawData& data,
                      const ta::foundation::math::CameraFrame& camera,
                      const float viewport_width,
                      const float viewport_height,
                      const float scale_factor,
                      const ta::features::esp::EspSettingsSnapshot& settings,
                      const ta::features::esp::EspWorldSnapshot& snapshot,
                      const Entry& entry,
                      const ta::foundation::RgbaColor& color,
                      TextBuilder&& make_text,
                      const float y_offset = 0.0f)
    {
        ta::foundation::math::Vector3 screen_pos{};
        if (!ta::foundation::math::project_visible(
                camera,
                entry.pos3d,
                viewport_width,
                viewport_height,
                screen_pos))
        {
            return;
        }

        data.labels.push_back({
            ImVec2(screen_pos.x, screen_pos.y + y_offset),
            ta::presentation::imgui::to_imgui(color),
            make_text(static_cast<int>(snapshot.local_position.distance(entry.pos3d)), entry.name),
            true,
            static_cast<float>(settings.font_size) * scale_factor
        });
    }
}

    EspOverlayDrawData build_esp_overlay(const ta::features::esp::EspSettingsSnapshot& settings,
                                         const ta::features::esp::EspWorldSnapshot& snapshot,
                                         const ta::foundation::math::CameraFrame& camera)
    {
        EspOverlayDrawData data{};
        const bool has_data = !snapshot.exfils.empty() || !snapshot.transits.empty() ||
                              !snapshot.players.empty() || !snapshot.loot.empty();
        if (!has_data || !camera.valid)
            return data;

        const auto viewport = ta::application::presentation_state::viewport.acquire();
        if (!viewport.metrics.valid())
            return data;

        const bool any_enabled = settings.extracts_enabled ||
                                 settings.transits_enabled ||
                                 settings.pmc_enabled ||
                                 settings.scav_enabled ||
                                 settings.draw_any_loot();
        if (!any_enabled)
            return data;

        const float scale_factor = viewport.scale_factor;
        data.labels.reserve(
            snapshot.exfils.size() +
            snapshot.transits.size() +
            snapshot.players.size() +
            snapshot.loot.size());

        if (settings.extracts_enabled)
        {
            for (const auto& exfil : snapshot.exfils)
            {
                append_point(data, camera, viewport.metrics.width, viewport.metrics.height, scale_factor,
                    settings, snapshot, exfil, settings.extracts_color,
                    [&exfil](const int distance, const std::string& name)
                    {
                        return distance_label(static_cast<float>(distance), name) +
                               " (" + exfil_status_string(exfil.status) + ")";
                    });
            }
        }

        if (settings.transits_enabled)
        {
            for (const auto& transit : snapshot.transits)
            {
                append_point(data, camera, viewport.metrics.width, viewport.metrics.height, scale_factor,
                    settings, snapshot, transit, settings.transits_color,
                    [](const int distance, const std::string& name)
                    {
                        return distance_label(static_cast<float>(distance), name);
                    });
            }
        }

        if (settings.pmc_enabled || settings.scav_enabled)
        {
            const float y_offset = 10.0f * scale_factor;
            for (const auto& player : snapshot.players)
            {
                const bool enabled = player.pmc ? settings.pmc_enabled : settings.scav_enabled;
                if (!enabled)
                    continue;

                append_point(data, camera, viewport.metrics.width, viewport.metrics.height, scale_factor,
                    settings, snapshot, player,
                    player.pmc ? settings.pmc_color : settings.scav_color,
                    [](const int distance, const std::string& name)
                    {
                        return distance_label(static_cast<float>(distance), name);
                    },
                    y_offset);
            }
        }

        if (settings.draw_any_loot())
        {
            for (const auto& loot : snapshot.loot)
            {
                const ta::features::esp::EspLootStyle style = ta::features::esp::resolve_esp_loot_style(settings, loot.type);
                if (!style.enabled)
                    continue;

                const float distance_sq = ta::foundation::math::distance_sq(snapshot.local_position, loot.pos3d);
                if (distance_sq > style.max_distance * style.max_distance)
                    continue;

                append_projected_label(
                    data,
                    camera,
                    viewport.metrics.width,
                    viewport.metrics.height,
                    loot.pos3d,
                    ta::presentation::imgui::to_imgui(style.color),
                    distance_label(std::sqrt(distance_sq), loot.name),
                    false);
            }
        }

        return data;
    }
}
