#pragma once
#include <imgui.h>
#include <imgui_stdlib.h>
#include <algorithm>
#include <cfloat>
#include <cmath>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>

#include "menu.h"
#include "../../util/i18n.h"

namespace imgui_addons
{
    // ---------- small helpers ----------

    inline ImU32 lerp_col(const ImVec4& a, const ImVec4& b, float t)
    {
        return ImGui::GetColorU32(ImVec4(
            a.x + (b.x - a.x) * t,
            a.y + (b.y - a.y) * t,
            a.z + (b.z - a.z) * t,
            a.w + (b.w - a.w) * t));
    }

    // per-widget animation state (eased toward target each frame)
    inline float animate(ImGuiID id, float target, float speed = 16.f)
    {
        static std::unordered_map<ImGuiID, float> state;
        float& v = state[id];
        v += (target - v) * (std::min)(1.f, ImGui::GetIO().DeltaTime * speed);
        return v;
    }

    inline std::string label_text(const std::string& label)
    {
        return label.substr(0, label.find("##"));
    }

    inline void push_disabled_dim()
    {
        ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.45f);
    }

    inline void safety_bullet_text(const ImVec4& col, const char* text)
    {
        const float wrap_w = ImGui::GetContentRegionAvail().x;
        ImGui::PushStyleColor(ImGuiCol_Text, col);
        ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + wrap_w);
        ImGui::Bullet();
        ImGui::SameLine();
        ImGui::TextUnformatted(text);
        ImGui::PopTextWrapPos();
        ImGui::PopStyleColor();
    }

    inline bool begin_safety_tooltip()
    {
        static ImVec2 locked_pos{};
        static bool had_tooltip = false;
        ImGuiIO& io = ImGui::GetIO();
        const bool use_locked_pos = io.KeyCtrl && had_tooltip;

        const float sf = menu::get_scale_factor();
        const ImGuiStyle& style = ImGui::GetStyle();
        const float extra_h_pad = 10.f * sf;
        ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,
            ImVec2(style.WindowPadding.x + extra_h_pad, style.WindowPadding.y));

        const ImVec4 warn_tint = ImVec4(0.92f, 0.42f, 0.10f, 1.f);
        const ImVec4 popup_bg = style.Colors[ImGuiCol_PopupBg];
        ImGui::PushStyleColor(ImGuiCol_PopupBg, lerp_col(popup_bg, warn_tint, 0.18f));
        if (use_locked_pos)
            ImGui::SetNextWindowPos(locked_pos);

        const bool visible = ImGui::Begin("##Tooltip_Safety", nullptr,
            ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
            ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize);
        ImGui::PopStyleColor();

        if (!visible)
        {
            had_tooltip = false;
            ImGui::End();
            ImGui::PopStyleVar();
            return false;
        }

        ImGui::SeparatorText(i18n::tr("safety_check_inf").c_str());
        locked_pos = ImGui::GetWindowPos();
        had_tooltip = true;
        return true;
    }

    inline void end_safety_tooltip()
    {
        ImGui::End();
        ImGui::PopStyleVar();
    }

    inline void draw_safety_tooltip_body()
    {
        safety_bullet_text(menu::palette::txt,
            i18n::tr("safety_warning_inf").c_str());
        ImGui::Spacing();
        safety_bullet_text(menu::with_alpha(menu::palette::txt, 0.88f),
            i18n::tr("safety_understand_inf").c_str());

        ImGui::Spacing();
        ImGui::Separator();
        ImGui::Spacing();

        safety_bullet_text(menu::palette::muted,
            i18n::tr("safety_ctrl_click_inf").c_str());

        ImGui::Spacing();
        safety_bullet_text(menu::palette::accent2, i18n::tr("play_it_safe_inf").c_str());
    }

    inline void show_safety_tooltip()
    {
        if (!begin_safety_tooltip())
            return;

        draw_safety_tooltip_body();
        end_safety_tooltip();
    }

    // ---------- modern widgets ----------

    // animated toggle switch (replaces the old checkbox)
    static bool checkbox(const std::string& label, bool* v, const bool disabled = false,
                         const bool master = false, const bool safety_switch = false)
    {
        const float sf = menu::get_scale_factor();
        ImGui::PushID(label.c_str());

        const std::string txt = label_text(label);
        const float row_h = master ? 28.f * sf : 24.f * sf;
        const ImVec2 start = ImGui::GetCursorScreenPos();
        const float avail = ImGui::GetContentRegionAvail().x;

        if (disabled) push_disabled_dim();
        ImGui::InvisibleButton("row", ImVec2(avail, row_h));
        const bool hovered = !disabled && ImGui::IsItemHovered();
        bool clicked = !disabled && ImGui::IsItemClicked();
        if (clicked && safety_switch && !*v && !ImGui::GetIO().KeyCtrl)
            clicked = false;
        if (clicked) *v = !*v;

        if (safety_switch && !disabled && (hovered || (ImGui::IsItemClicked() && !*v && !ImGui::GetIO().KeyCtrl)))
            show_safety_tooltip();

        const float a = animate(ImGui::GetID("t"), *v ? 1.f : 0.f);
        ImDrawList* dl = ImGui::GetWindowDrawList();

        float label_x = start.x;
        if (master)
        {
            const float bar_h = row_h - 4.f * sf;
            dl->AddRectFilled(
                ImVec2(start.x, start.y + 2.f * sf),
                ImVec2(start.x + 3.f * sf, start.y + 2.f * sf + bar_h),
                ImGui::GetColorU32(menu::with_alpha(menu::palette::accent, disabled ? 0.35f : 0.95f)),
                2.f);
            label_x += 8.f * sf;
        }

        ImFont* label_font = master ? menu::get_bold_font() : ImGui::GetFont();
        const float font_size = master ? label_font->LegacySize * 1.04f : ImGui::GetFontSize();
        const ImVec2 ts = label_font->CalcTextSizeA(font_size, FLT_MAX, 0.f, txt.c_str());
        const ImU32 label_col = disabled
            ? ImGui::GetColorU32(menu::with_alpha(menu::palette::muted, 0.7f))
            : master
                ? ImGui::GetColorU32(hovered ? menu::palette::accent2 : menu::palette::txt)
                : ImGui::GetColorU32(hovered ? menu::palette::txt : menu::with_alpha(menu::palette::txt, 0.9f));
        dl->AddText(label_font, font_size,
            ImVec2(label_x, start.y + (row_h - ts.y) * 0.5f), label_col, txt.c_str());

        // track
        const float tw = 40.f * sf, th = 20.f * sf;
        const float tx = start.x + avail - tw;
        const float ty = start.y + (row_h - th) * 0.5f;
        const ImU32 track = lerp_col(menu::palette::surface2, menu::palette::accent, a);
        dl->AddRectFilled(ImVec2(tx, ty), ImVec2(tx + tw, ty + th), track, th * 0.5f);

        // knob
        const float kr = th * 0.5f - 2.5f * sf;
        const float kx = tx + th * 0.5f + a * (tw - th);
        dl->AddCircleFilled(ImVec2(kx, ty + th * 0.5f), kr, ImGui::GetColorU32(ImVec4(1, 1, 1, 1)));

        if (disabled) ImGui::PopStyleVar();
        ImGui::PopID();
        return clicked;
    }

    // custom slider track+knob with draggable handle
    inline bool slider_core(const std::string& label, float* value, float vmin, float vmax, bool is_int,
                            const char* fmt, const bool disabled = false)
    {
        const float sf = menu::get_scale_factor();
        ImGui::PushID(label.c_str());

        const std::string txt = label_text(label);

        if (disabled) push_disabled_dim();

        // label + value line
        ImGui::AlignTextToFramePadding();
        ImGui::TextColored(disabled ? menu::with_alpha(menu::palette::muted, 0.7f) : menu::palette::txt,
                           "%s", txt.c_str());

        char vbuf[32];
        if (is_int) snprintf(vbuf, sizeof(vbuf), "%d", static_cast<int>(lroundf(*value)));
        else snprintf(vbuf, sizeof(vbuf), fmt, *value);
        const ImVec2 vts = ImGui::CalcTextSize(vbuf);
        ImGui::SameLine(ImGui::GetWindowWidth() - vts.x - 14.f * sf);
        ImGui::TextColored(menu::palette::accent2, "%s", vbuf);

        // track
        const ImVec2 p = ImGui::GetCursorScreenPos();
        const float track_w = (std::max)(10.f, ImGui::GetContentRegionAvail().x - 14.f * sf);
        const float hit_h = 16.f * sf;
        const float track_h = 5.f * sf;

        ImGui::InvisibleButton("s", ImVec2(track_w, hit_h));
        const bool active = !disabled && ImGui::IsItemActive();
        const bool hovered = !disabled && ImGui::IsItemHovered();
        if (active)
        {
            float t = (ImGui::GetIO().MousePos.x - p.x) / track_w;
            t = t < 0.f ? 0.f : (t > 1.f ? 1.f : t);
            *value = vmin + t * (vmax - vmin);
            if (is_int) *value = static_cast<float>(lroundf(*value));
        }

        float t = (vmax > vmin) ? (*value - vmin) / (vmax - vmin) : 0.f;
        t = t < 0.f ? 0.f : (t > 1.f ? 1.f : t);

        const float cy = p.y + hit_h * 0.5f;
        ImDrawList* dl = ImGui::GetWindowDrawList();
        dl->AddRectFilled(ImVec2(p.x, cy - track_h * 0.5f), ImVec2(p.x + track_w, cy + track_h * 0.5f),
                          ImGui::GetColorU32(menu::palette::surface2), track_h * 0.5f);
        dl->AddRectFilled(ImVec2(p.x, cy - track_h * 0.5f), ImVec2(p.x + t * track_w, cy + track_h * 0.5f),
                          ImGui::GetColorU32(menu::palette::accent), track_h * 0.5f);
        const float kr = (active || hovered) ? 7.5f * sf : 6.f * sf;
        dl->AddCircleFilled(ImVec2(p.x + t * track_w, cy), kr + 1.f * sf,
                            ImGui::GetColorU32(menu::with_alpha(menu::palette::accent, 0.35f)));
        dl->AddCircleFilled(ImVec2(p.x + t * track_w, cy), kr, ImGui::GetColorU32(ImVec4(1, 1, 1, 1)));

        ImGui::Dummy(ImVec2(0, 4.f * sf));
        if (disabled) ImGui::PopStyleVar();
        ImGui::PopID();
        return active;
    }

    static bool slider_float(const std::string& label, float* v, float mn, float mx,
                             const bool disabled = false)
    {
        return slider_core(label, v, mn, mx, false, "%.2f", disabled);
    }

    static bool slider_int(const std::string& label, int* v, int mn, int mx, const bool disabled = false)
    {
        float f = static_cast<float>(*v);
        const bool r = slider_core(label, &f, static_cast<float>(mn), static_cast<float>(mx), true, "%.0f", disabled);
        *v = static_cast<int>(lroundf(f));
        return r;
    }

    // right-aligned control width helper
    inline float right_align(float ctrl_w)
    {
        const float sf = menu::get_scale_factor();
        ImGui::SameLine(ImGui::GetWindowWidth() - ctrl_w - 14.f * sf);
        ImGui::SetNextItemWidth(ctrl_w);
        return ctrl_w;
    }

    static bool slider_int_editable(const std::string& label, int* v, int mn, int slider_max,
                                    const bool disabled = false)
    {
        const float sf = menu::get_scale_factor();
        ImGui::PushID(label.c_str());

        const std::string txt = label_text(label);

        if (disabled) push_disabled_dim();

        ImGui::AlignTextToFramePadding();
        ImGui::TextColored(disabled ? menu::with_alpha(menu::palette::muted, 0.7f) : menu::palette::txt,
                           "%s", txt.c_str());

        const float input_w = (std::min)(110.f * sf, ImGui::GetContentRegionAvail().x * 0.35f);
        right_align(input_w);
        int display_value = *v;
        if (disabled) ImGui::BeginDisabled();
        const bool edited = ImGui::InputInt("##val", &display_value, 0, 0);
        if (disabled) ImGui::EndDisabled();
        if (edited && !disabled)
            *v = (std::max)(mn, display_value);

        const float track_w = (std::max)(10.f, ImGui::GetContentRegionAvail().x - 14.f * sf);
        const float hit_h = 16.f * sf;
        const float track_h = 5.f * sf;
        const ImVec2 p = ImGui::GetCursorScreenPos();

        ImGui::InvisibleButton("s", ImVec2(track_w, hit_h));
        const bool active = !disabled && ImGui::IsItemActive();
        const bool hovered = !disabled && ImGui::IsItemHovered();
        if (active)
        {
            float t = (ImGui::GetIO().MousePos.x - p.x) / track_w;
            t = t < 0.f ? 0.f : (t > 1.f ? 1.f : t);
            const float raw = static_cast<float>(mn) + t * static_cast<float>(slider_max - mn);
            *v = static_cast<int>(lroundf(raw));
        }

        const float clamped = static_cast<float>((std::min)(*v, slider_max));
        float t = (slider_max > mn)
            ? (clamped - static_cast<float>(mn)) / static_cast<float>(slider_max - mn)
            : 0.f;
        t = t < 0.f ? 0.f : (t > 1.f ? 1.f : t);

        const float cy = p.y + hit_h * 0.5f;
        ImDrawList* dl = ImGui::GetWindowDrawList();
        dl->AddRectFilled(ImVec2(p.x, cy - track_h * 0.5f), ImVec2(p.x + track_w, cy + track_h * 0.5f),
                          ImGui::GetColorU32(menu::palette::surface2), track_h * 0.5f);
        dl->AddRectFilled(ImVec2(p.x, cy - track_h * 0.5f), ImVec2(p.x + t * track_w, cy + track_h * 0.5f),
                          ImGui::GetColorU32(menu::palette::accent), track_h * 0.5f);
        const float kr = (active || hovered) ? 7.5f * sf : 6.f * sf;
        dl->AddCircleFilled(ImVec2(p.x + t * track_w, cy), kr + 1.f * sf,
                            ImGui::GetColorU32(menu::with_alpha(menu::palette::accent, 0.35f)));
        dl->AddCircleFilled(ImVec2(p.x + t * track_w, cy), kr, ImGui::GetColorU32(ImVec4(1, 1, 1, 1)));

        ImGui::Dummy(ImVec2(0, 4.f * sf));
        if (disabled) ImGui::PopStyleVar();
        ImGui::PopID();
        return active || edited;
    }

    static bool input_float(const char* label, float* v, const float step = 0.1f, const bool disabled = false)
    {
        const float sf = menu::get_scale_factor();
        ImGui::PushID(label);
        if (disabled) push_disabled_dim();
        const std::string label_txt = i18n::tr(label);
        ImGui::AlignTextToFramePadding();
        ImGui::TextColored(disabled ? menu::with_alpha(menu::palette::muted, 0.7f) : menu::palette::txt,
                           "%s", label_txt.c_str());
        const float input_w = (std::min)(130.f * sf, ImGui::GetContentRegionAvail().x * 0.42f);
        right_align(input_w);
        if (disabled) ImGui::BeginDisabled();
        const bool edited = ImGui::InputFloat("##val", v, step, step * 10.f, "%.3f");
        if (disabled) ImGui::EndDisabled();
        ImGui::Dummy(ImVec2(0, 2.f * sf));
        if (disabled) ImGui::PopStyleVar();
        ImGui::PopID();
        return edited;
    }

    static bool select(const std::string& label, int* v, const std::vector<const char*>& options,
                       const bool disabled = false, const bool translate_options = true)
    {
        const float sf = menu::get_scale_factor();
        ImGui::PushID(label.c_str());
        const std::string raw_label = label_text(label);
        const std::string combo_label = label.find("##") != std::string::npos
            ? raw_label
            : i18n::tr(raw_label);
        ImGui::AlignTextToFramePadding();
        ImGui::TextColored(disabled ? menu::with_alpha(menu::palette::muted, 0.7f) : menu::palette::txt,
                           "%s", combo_label.c_str());
        const float w = (std::min)(230.f * sf, ImGui::GetContentRegionAvail().x * 0.62f);
        right_align(w);
        if (disabled) ImGui::BeginDisabled();
        bool r = false;
        if (!translate_options)
        {
            r = ImGui::Combo("##c", v, options.data(), static_cast<int32_t>(options.size()));
        }
        else
        {
            struct combo_option_cache
            {
                std::vector<std::string> strings;
                std::vector<const char*> ptrs;
            };
            static thread_local std::unordered_map<uint32_t, combo_option_cache> caches;
            const uint32_t combo_id = ImGui::GetID("##c");
            combo_option_cache& cache = caches[combo_id];
            cache.strings.clear();
            cache.ptrs.clear();
            cache.strings.reserve(options.size());
            cache.ptrs.reserve(options.size());
            for (const char* option : options)
            {
                if (!option)
                    cache.strings.emplace_back();
                else
                    cache.strings.push_back(i18n::tr(option));
                cache.ptrs.push_back(cache.strings.back().c_str());
            }
            r = ImGui::Combo("##c", v, cache.ptrs.data(), static_cast<int32_t>(cache.ptrs.size()));
        }
        if (disabled) ImGui::EndDisabled();
        ImGui::Dummy(ImVec2(0, 3.f * sf));
        ImGui::PopID();
        return r;
    }

    static bool colorpicker(const std::string& label, ImVec4* v, const bool disabled = false)
    {
        const float sf = menu::get_scale_factor();
        ImGui::PushID(label.c_str());
        if (disabled) push_disabled_dim();
        ImGui::AlignTextToFramePadding();
        ImGui::TextColored(disabled ? menu::with_alpha(menu::palette::muted, 0.7f) : menu::palette::txt,
                           "%s", label_text(label).c_str());
        const float sw = 40.f * sf;
        ImGui::SameLine(ImGui::GetWindowWidth() - sw - 14.f * sf);
        ImGui::SetNextItemWidth(sw);
        if (disabled) ImGui::BeginDisabled();
        const bool r = ImGui::ColorEdit4("##col", reinterpret_cast<float*>(v),
                                         ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel |
                                         ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_PickerHueWheel);
        if (disabled) ImGui::EndDisabled();
        ImGui::Dummy(ImVec2(0, 3.f * sf));
        if (disabled) ImGui::PopStyleVar();
        ImGui::PopID();
        return r;
    }

    static bool int_textbot(const std::string& label, int* v)
    {
        const float sf = menu::get_scale_factor();
        ImGui::PushID(label.c_str());
        ImGui::AlignTextToFramePadding();
        ImGui::TextColored(menu::palette::txt, "%s", label_text(label).c_str());
        const float w = (std::min)(150.f * sf, ImGui::GetContentRegionAvail().x * 0.5f);
        right_align(w);
        const bool r = ImGui::InputInt("##i", v);
        ImGui::Dummy(ImVec2(0, 3.f * sf));
        ImGui::PopID();
        return r;
    }

    static bool textbox(const std::string& label, std::string* v)
    {
        const float sf = menu::get_scale_factor();
        ImGui::PushID(label.c_str());
        ImGui::AlignTextToFramePadding();
        ImGui::TextColored(menu::palette::txt, "%s", label_text(label).c_str());
        const float w = (std::min)(230.f * sf, ImGui::GetContentRegionAvail().x * 0.62f);
        right_align(w);
        const bool r = ImGui::InputText("##t", v);
        ImGui::Dummy(ImVec2(0, 3.f * sf));
        ImGui::PopID();
        return r;
    }

    static bool compact_textbox(const std::string& label, std::string* v, const size_t max_chars = 3,
                                const bool disabled = false)
    {
        const float sf = menu::get_scale_factor();
        ImGui::PushID(label.c_str());
        ImGui::AlignTextToFramePadding();
        ImGui::TextColored(disabled ? menu::with_alpha(menu::palette::muted, 0.7f) : menu::palette::txt,
                           "%s", i18n::tr(label_text(label)).c_str());
        const float w = (8.f * static_cast<float>(max_chars) + 14.f) * sf;
        right_align(w);
        if (disabled)
            ImGui::BeginDisabled();
        ImGui::SetNextItemWidth(w);
        bool changed = ImGui::InputText("##t", v);
        if (v->size() > max_chars)
            v->resize(max_chars);
        if (disabled)
            ImGui::EndDisabled();
        ImGui::Dummy(ImVec2(0, 3.f * sf));
        ImGui::PopID();
        return changed;
    }
}