#pragma once

#include <algorithm>
#include <cmath>
#include <string>
#include <vector>

#include <imgui.h>

#include "../gui/menu/imgui_addons.h"
#include "../gui/menu/menu.h"

class ConfigModule;

namespace esp_text
{
    struct settings
    {
        bool outline = false;
        bool background = false;
        bool clustering = false;
        bool depth_sort = false;
    };

    inline ConfigModule* config_module = nullptr;

    settings current_settings();
    bool minimap_marker_outline_enabled();
    float esp_font_size();
    float quest_esp_font_size();
    float minimap_icon_size();
    float minimap_label_font_size();

    struct minimap_marker_settings
    {
        std::string player;
        std::string entity;
        std::string quest;
        std::string exfil;
        std::string transit;
        std::string height_up;
        std::string height_down;

        float size_player = 10.f;
        float size_entity = 7.f;
        float size_quest = 10.f;
        float size_exfil = 10.f;
        float size_transit = 10.f;
        float size_height_up = 7.f;
        float size_height_down = 7.f;
    };

    const minimap_marker_settings& minimap_markers();
    void begin_frame();
    void queue_centered(const ImVec2& pos, const ImVec4& col, const std::string& text, float distance, float font_size);
    void queue_topleft(const ImVec2& top_left, const ImVec4& col, const std::string& text, float distance, float font_size);
    void flush_pending(ImDrawList* draw_list, ImFont* font);

    inline bool any_enabled(const settings& s)
    {
        return s.outline || s.background || s.clustering;
    }

    inline bool visual_effects_enabled(const settings& s)
    {
        return s.outline || s.background;
    }

    struct label
    {
        ImVec2 pos{};
        ImVec4 color{};
        std::string text;
        float distance = 0.f;
        int price = 0;
    };

    inline ImVec2 text_size(ImFont* font, const float font_size, const std::string& text)
    {
        if (font)
            return font->CalcTextSizeA(font_size, FLT_MAX, 0.f, text.c_str());
        return ImGui::CalcTextSize(text.c_str());
    }

    inline ImVec4 outline_color()
    {
        return {0.f, 0.f, 0.f, 0.92f};
    }

    inline ImVec4 background_color()
    {
        return {0.f, 0.f, 0.f, 0.55f};
    }

    inline void draw_outlined(ImDrawList* draw_list, ImFont* font, const float font_size,
                              const ImVec2& top_left, const ImVec4& col, const char* text)
    {
        static constexpr ImVec2 offsets[] = {
            {-1.f, -1.f}, {0.f, -1.f}, {1.f, -1.f},
            {-1.f, 0.f}, {1.f, 0.f},
            {-1.f, 1.f}, {0.f, 1.f}, {1.f, 1.f}
        };

        const ImU32 outline_col = ImGui::GetColorU32(outline_color());
        const ImU32 main_col = ImGui::GetColorU32(col);

        for (const ImVec2& off : offsets)
        {
            const ImVec2 p = {top_left.x + off.x, top_left.y + off.y};
            if (font)
                draw_list->AddText(font, font_size, p, outline_col, text);
            else
                draw_list->AddText(p, outline_col, text);
        }

        if (font)
            draw_list->AddText(font, font_size, top_left, main_col, text);
        else
            draw_list->AddText(top_left, main_col, text);
    }

    inline void draw_centered(ImDrawList* draw_list, ImFont* font, const float font_size,
                              ImVec2 center, const ImVec4& col, const std::string& text,
                              const settings& s)
    {
        const ImVec2 size = text_size(font, font_size, text);
        const float pad_x = 4.f * menu::get_scale_factor();
        const float pad_y = 2.f * menu::get_scale_factor();
        const ImVec2 top_left = {center.x - size.x * 0.5f, center.y - size.y * 0.5f};

        if (s.background)
        {
            draw_list->AddRectFilled(
                {top_left.x - pad_x, top_left.y - pad_y},
                {top_left.x + size.x + pad_x, top_left.y + size.y + pad_y},
                ImGui::GetColorU32(background_color()), 4.f);
        }

        if (s.outline)
            draw_outlined(draw_list, font, font_size, top_left, col, text.c_str());
        else if (font)
            draw_list->AddText(font, font_size, top_left, ImGui::GetColorU32(col), text.c_str());
        else
            draw_list->AddText(top_left, ImGui::GetColorU32(col), text.c_str());
    }

    inline void apply_clustering(std::vector<label>& labels, ImFont* font, const float font_size)
    {
        if (labels.size() < 2) return;

        const float radius = 28.f * menu::get_scale_factor();
        std::vector<int> group_id(labels.size(), -1);
        int next_group = 0;

        for (size_t i = 0; i < labels.size(); ++i)
        {
            if (group_id[i] != -1) continue;
            group_id[i] = next_group;
            for (size_t j = i + 1; j < labels.size(); ++j)
            {
                const float dx = labels[i].pos.x - labels[j].pos.x;
                const float dy = labels[i].pos.y - labels[j].pos.y;
                if (dx * dx + dy * dy <= radius * radius)
                    group_id[j] = next_group;
            }
            ++next_group;
        }

        std::vector<label> merged;
        merged.reserve(labels.size());

        for (int g = 0; g < next_group; ++g)
        {
            std::vector<const label*> group;
            for (size_t i = 0; i < labels.size(); ++i)
            {
                if (group_id[i] == g)
                    group.push_back(&labels[i]);
            }

            if (group.size() <= 1)
            {
                merged.push_back(*group.front());
                continue;
            }

            label combined = *group.front();
            float cx = 0.f;
            float cy = 0.f;
            int best_price = combined.price;
            const label* best = group.front();

            for (const label* entry : group)
            {
                cx += entry->pos.x;
                cy += entry->pos.y;
                if (entry->price > best_price)
                {
                    best_price = entry->price;
                    best = entry;
                }
            }

            cx /= static_cast<float>(group.size());
            cy /= static_cast<float>(group.size());
            combined.pos = {cx, cy};
            combined.color = best->color;
            combined.price = best_price;
            combined.distance = best->distance;

            char buf[128];
            if (group.size() > 1)
                snprintf(buf, sizeof(buf), "%s (+%zu)", best->text.c_str(), group.size() - 1);
            else
                snprintf(buf, sizeof(buf), "%s", best->text.c_str());
            combined.text = buf;

            merged.push_back(std::move(combined));
        }

        labels = std::move(merged);
    }

    inline void draw_labels(ImDrawList* draw_list, ImFont* font, const float font_size,
                            std::vector<label>& labels, const settings& s)
    {
        if (labels.empty()) return;

        if (s.clustering)
            apply_clustering(labels, font, font_size);

        if (s.depth_sort)
        {
            for (const auto& entry : labels)
                queue_centered(entry.pos, entry.color, entry.text, entry.distance, font_size);
            return;
        }

        for (const auto& entry : labels)
            draw_centered(draw_list, font, font_size, entry.pos, entry.color, entry.text, s);
    }

    inline void draw_topleft(ImDrawList* draw_list, ImFont* font, const float font_size,
                             const ImVec2& top_left, const ImVec4& col, const std::string& text,
                             const settings& s)
    {
        const ImVec2 size = text_size(font, font_size, text);
        const float pad_x = 3.f * menu::get_scale_factor();
        const float pad_y = 1.f * menu::get_scale_factor();

        if (s.background)
        {
            draw_list->AddRectFilled(
                {top_left.x - pad_x, top_left.y - pad_y},
                {top_left.x + size.x + pad_x, top_left.y + size.y + pad_y},
                ImGui::GetColorU32(background_color()), 3.f);
        }

        if (s.outline)
            draw_outlined(draw_list, font, font_size, top_left, col, text.c_str());
        else if (font)
            draw_list->AddText(font, font_size, top_left, ImGui::GetColorU32(col), text.c_str());
        else
            draw_list->AddText(top_left, ImGui::GetColorU32(col), text.c_str());
    }

    inline void draw_simple(ImDrawList* draw_list, ImFont* font, const float font_size,
                            const ImVec2& pos, const ImVec4& col, const std::string& text,
                            const float distance = 0.f)
    {
        const settings s = current_settings();
        if (s.depth_sort)
        {
            queue_centered(pos, col, text, distance, font_size);
            return;
        }

        const ImVec2 size = text_size(font, font_size, text);
        const ImVec2 top_left = {pos.x - size.x * 0.5f, pos.y - size.y * 0.5f};

        if (visual_effects_enabled(s))
            draw_topleft(draw_list, font, font_size, top_left, col, text, s);
        else if (font)
            draw_list->AddText(font, font_size, top_left, ImGui::GetColorU32(col), text.c_str());
        else
            draw_list->AddText(top_left, ImGui::GetColorU32(col), text.c_str());
    }

    inline void draw_simple_topleft(ImDrawList* draw_list, ImFont* font, const float font_size,
                                    const ImVec2& top_left, const ImVec4& col, const std::string& text,
                                    const float distance = 0.f)
    {
        const settings s = current_settings();
        if (s.depth_sort)
        {
            queue_topleft(top_left, col, text, distance, font_size);
            return;
        }

        if (visual_effects_enabled(s))
            draw_topleft(draw_list, font, font_size, top_left, col, text, s);
        else if (font)
            draw_list->AddText(font, font_size, top_left, ImGui::GetColorU32(col), text.c_str());
        else
            draw_list->AddText(top_left, ImGui::GetColorU32(col), text.c_str());
    }
}