#include "presentation/widgets/BasicWidgets.h"

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <string>
#include <utility>
#include <vector>

#include <imgui.h>
#include <imgui_stdlib.h>

#include "foundation/input/KeyNames.h"
#include "presentation/localization/UiLocalizationService.h"
#include "presentation/runtime/Viewport.h"
#include "presentation/theme/MenuTheme.h"

namespace ta::presentation::widgets
{
    namespace
    {
        ImGuiID g_keybind_capture_id = 0;
        int g_keybind_capture_start_frame = -1;
        std::vector<bool> g_field_row_tables;

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

        void draw_wrapped_label(const std::string& text)
        {
            ImGui::PushTextWrapPos(0.0f);
            ImGui::TextUnformatted(text.c_str());
            ImGui::PopTextWrapPos();
        }

        float limited_button_width(const std::string& label,
                                   const float max_width_unscaled,
                                   const float min_width_unscaled)
        {
            const float sf = ta::presentation::runtime::scale_factor();
            const std::string text = visible_label(label);
            const float text_width = ImGui::CalcTextSize(text.c_str()).x + ImGui::GetStyle().FramePadding.x * 2.0f;
            return (std::min)(max_width_unscaled * sf, (std::max)(min_width_unscaled * sf, text_width));
        }
    }

    TwoColumnLayout two_column_layout(const float minimum_column_width_unscaled)
    {
        const ta::presentation::theme::UiMetrics metrics = ta::presentation::theme::ui_metrics();
        const float available = ImGui::GetContentRegionAvail().x;

        TwoColumnLayout layout{};
        layout.gap = metrics.spacing_md;
        layout.height = ImGui::GetContentRegionAvail().y;
        layout.stacked = available < minimum_column_width_unscaled * metrics.scale * 2.0f + layout.gap;
        layout.width = layout.stacked ? available : (available - layout.gap) * 0.5f;
        return layout;
    }

    bool compact_layout(const float minimum_width_unscaled)
    {
        return ta::presentation::theme::compact_for_width(
            ImGui::GetContentRegionAvail().x,
            minimum_width_unscaled);
    }

    bool begin_field_row(const std::string& label)
    {
        const std::string text = visible_label(label);
        const ta::presentation::theme::UiMetrics metrics = ta::presentation::theme::ui_metrics();
        const bool stacked = ImGui::GetContentRegionAvail().x < metrics.form_stack_width;

        ImGui::PushID(label.c_str());
        ImGui::BeginGroup();

        if (stacked)
        {
            draw_wrapped_label(text);
            ImGui::Spacing();
            ImGui::SetNextItemWidth(-1.0f);
            g_field_row_tables.push_back(false);
            return true;
        }

        if (!ImGui::BeginTable("##field_row", 2,
                               ImGuiTableFlags_SizingStretchProp |
                               ImGuiTableFlags_NoSavedSettings))
        {
            ImGui::EndGroup();
            ImGui::PopID();
            return false;
        }

        ImGui::TableSetupColumn("##label", ImGuiTableColumnFlags_WidthStretch, 0.42f);
        ImGui::TableSetupColumn("##control", ImGuiTableColumnFlags_WidthStretch, 0.58f);
        ImGui::TableNextRow();
        ImGui::TableSetColumnIndex(0);
        ImGui::AlignTextToFramePadding();
        draw_wrapped_label(text);
        ImGui::TableSetColumnIndex(1);
        ImGui::SetNextItemWidth(-1.0f);
        g_field_row_tables.push_back(true);
        return true;
    }

    void end_field_row()
    {
        if (g_field_row_tables.empty())
            return;

        const bool used_table = g_field_row_tables.back();
        g_field_row_tables.pop_back();
        if (used_table)
            ImGui::EndTable();
        ImGui::EndGroup();
        ImGui::PopID();
    }

    bool action_button(const std::string& label,
                       const float max_width_unscaled,
                       const float min_width_unscaled)
    {
        const std::string text = visible_label(label);
        const float width = limited_button_width(label, max_width_unscaled, min_width_unscaled);
        const bool pressed = ImGui::Button(label.c_str(), ImVec2(width, 0.0f));
        const float full_width = ImGui::CalcTextSize(text.c_str()).x + ImGui::GetStyle().FramePadding.x * 2.0f;
        if (ImGui::IsItemHovered() && full_width > width)
            ImGui::SetTooltip("%s", text.c_str());
        return pressed;
    }

    bool toggle_button(const std::string& id, bool* value, const float width_unscaled)
    {
        if (!value)
            return false;

        const std::string state_text = ta::presentation::localization::tr(*value ? "ENABLED" : "DISABLED");
        const std::string label = state_text + "##" + id;
        const float width = width_unscaled * ta::presentation::runtime::scale_factor();

        ImGui::PushStyleColor(
            ImGuiCol_Button,
            *value
                ? ta::presentation::theme::with_alpha(ta::presentation::theme::palette::success, 0.24f)
                : ImGui::GetStyle().Colors[ImGuiCol_FrameBg]);
        ImGui::PushStyleColor(
            ImGuiCol_ButtonHovered,
            *value
                ? ta::presentation::theme::with_alpha(ta::presentation::theme::palette::success, 0.38f)
                : ta::presentation::theme::with_alpha(ta::presentation::theme::palette::accent, 0.28f));
        ImGui::PushStyleColor(
            ImGuiCol_Text,
            *value
                ? ta::presentation::theme::palette::success
                : ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]);

        const bool clicked = ImGui::Button(label.c_str(), ImVec2(width, 0.0f));
        ImGui::PopStyleColor(3);
        if (clicked)
            *value = !*value;
        return clicked;
    }

    void section_header(const std::string& title, const bool separator)
    {
        ImGui::TextColored(ta::presentation::theme::palette::accent, "%s", title.c_str());
        if (separator)
            ImGui::Separator();
        ImGui::Spacing();
    }

    void empty_state(const char* title_key, const char* hint_key)
    {
        ImGui::Spacing();
        ImGui::TextWrapped("%s", ta::presentation::localization::tr(title_key ? title_key : "").c_str());
        if (hint_key && *hint_key)
            hint_text(hint_key);
        ImGui::Spacing();
    }

    void centered_text(ImDrawList* draw_list,
                       const ImVec2& pos,
                       const ImVec4 color,
                       const std::string& text,
                       ImFont* font,
                       const float font_size)
    {
        if (!draw_list || text.empty())
            return;

        const ImVec2 size = ImGui::CalcTextSize(text.c_str());
        const ImVec2 centered{pos.x - size.x / 2.0f, pos.y - size.y / 2.0f};
        const ImU32 packed_color = ImGui::GetColorU32(color);

        if (font && font_size > 0.0f)
            draw_list->AddText(font, font_size, centered, packed_color, text.c_str());
        else
            draw_list->AddText(centered, packed_color, text.c_str());
    }

    bool select(const std::string& label, int* value, const std::vector<std::string>& options)
    {
        if (!value || !begin_field_row(label))
            return false;

        std::vector<std::string> translated;
        std::vector<const char*> displayed;
        translated.reserve(options.size());
        displayed.reserve(options.size());
        for (const auto& option : options)
        {
            translated.push_back(ta::presentation::localization::tr(option));
            displayed.push_back(translated.back().c_str());
        }
        const bool changed = ImGui::Combo("##value", value, displayed.data(), static_cast<int32_t>(displayed.size()));
        end_field_row();
        return changed;
    }

    bool keybind(const std::string& label, int* virtual_key, const bool allow_none, ta::application::IKeyInput& input)
    {
        if (!virtual_key || !begin_field_row(label))
            return false;

        const ImGuiID id = ImGui::GetID("##keybind_capture");
        const bool capturing = g_keybind_capture_id == id;
        const std::string button_text = capturing
            ? ta::presentation::localization::tr("PRESS A KEY")
            : ta::foundation::input::display_name(*virtual_key, allow_none);

        const float sf = ta::presentation::runtime::scale_factor();
        const float spacing = ImGui::GetStyle().ItemInnerSpacing.x;
        const float available = ImGui::GetContentRegionAvail().x;
        const std::string clear_text = allow_none
            ? ta::presentation::localization::tr("CLEAR")
            : std::string{};
        const float measured_clear_width = allow_none
            ? ImGui::CalcTextSize(clear_text.c_str()).x + ImGui::GetStyle().FramePadding.x * 2.0f
            : 0.0f;
        const float clear_width = allow_none
            ? (std::min)(measured_clear_width, (std::max)(36.0f * sf, available * 0.38f))
            : 0.0f;
        const float capture_width = allow_none
            ? (std::max)(1.0f, available - clear_width - spacing)
            : -1.0f;

        bool changed = false;
        if (ImGui::Button(button_text.c_str(), ImVec2(capture_width, 0.0f)))
        {
            input.clear_pressed_state();
            g_keybind_capture_id = id;
            g_keybind_capture_start_frame = ImGui::GetFrameCount();
        }

        if (capturing && ImGui::IsItemHovered())
            ImGui::SetTooltip("%s", ta::presentation::localization::tr("Esc cancels").c_str());

        if (allow_none)
        {
            ImGui::SameLine(0.0f, spacing);
            if (ImGui::Button((clear_text + "##clear").c_str(), ImVec2(clear_width, 0.0f)))
            {
                if (g_keybind_capture_id == id)
                    g_keybind_capture_id = 0;
                input.clear_pressed_state();
                if (*virtual_key != 0)
                {
                    *virtual_key = 0;
                    changed = true;
                }
            }
        }

        if (g_keybind_capture_id == id && ImGui::GetFrameCount() > g_keybind_capture_start_frame)
        {
            if (input.pressed(0x1B))
            {
                g_keybind_capture_id = 0;
            }
            else if (const int pressed = input.first_pressed_key(); pressed != 0)
            {
                *virtual_key = pressed;
                g_keybind_capture_id = 0;
                changed = true;
            }
        }

        end_field_row();
        return changed;
    }

    bool slider_float(const std::string& label, float* value, const float min, const float max, const float step)
    {
        if (!value || !begin_field_row(label))
            return false;

        const float sf = ta::presentation::runtime::scale_factor();
        const float input_width = 78.0f * sf;
        const float spacing = ImGui::GetStyle().ItemInnerSpacing.x;
        const float available = ImGui::GetContentRegionAvail().x;
        const bool stacked_controls = available < 185.0f * sf;
        const float slider_width = stacked_controls
            ? available
            : (std::max)(40.0f * sf, available - input_width - spacing);
        const char* format = step >= 1.0f ? "%.0f" : "%.2f";

        ImGui::SetNextItemWidth(slider_width);
        bool changed = ImGui::SliderFloat("##slider", value, min, max, format, ImGuiSliderFlags_AlwaysClamp);

        if (!stacked_controls)
            ImGui::SameLine(0.0f, spacing);
        else
            ImGui::Spacing();
        ImGui::SetNextItemWidth(stacked_controls ? -1.0f : input_width);
        changed |= ImGui::InputFloat("##input", value, 0.0f, 0.0f, format, ImGuiInputTextFlags_EnterReturnsTrue);

        if (changed)
            *value = (std::clamp)(*value, min, max);

        end_field_row();
        return changed;
    }

    bool slider_int(const std::string& label, int* value, const int min, const int max)
    {
        if (!value || !begin_field_row(label))
            return false;

        const float sf = ta::presentation::runtime::scale_factor();
        const float input_width = 70.0f * sf;
        const float spacing = ImGui::GetStyle().ItemInnerSpacing.x;
        const float available = ImGui::GetContentRegionAvail().x;
        const bool stacked_controls = available < 175.0f * sf;
        const float slider_width = stacked_controls
            ? available
            : (std::max)(40.0f * sf, available - input_width - spacing);

        ImGui::SetNextItemWidth(slider_width);
        bool changed = ImGui::SliderInt("##slider", value, min, max, "%d", ImGuiSliderFlags_AlwaysClamp);

        if (!stacked_controls)
            ImGui::SameLine(0.0f, spacing);
        else
            ImGui::Spacing();
        ImGui::SetNextItemWidth(stacked_controls ? -1.0f : input_width);
        changed |= ImGui::InputInt("##input", value, 0, 0, ImGuiInputTextFlags_EnterReturnsTrue);

        if (changed)
            *value = (std::clamp)(*value, min, max);

        end_field_row();
        return changed;
    }

    bool colorpicker(const std::string& label, float* rgba)
    {
        if (!rgba || !begin_field_row(label))
            return false;

        const bool changed = ImGui::ColorEdit4("##value", rgba, ImGuiColorEditFlags_PickerHueWheel);
        end_field_row();
        return changed;
    }

    bool compact_colorpicker(const std::string& label, float* rgba)
    {
        if (!rgba)
            return false;

        ImGui::PushID(label.c_str());
        ImGui::SetNextItemWidth(86.0f * ta::presentation::runtime::scale_factor());
        const bool changed = ImGui::ColorEdit4(
            "##compact_color",
            rgba,
            ImGuiColorEditFlags_NoInputs |
            ImGuiColorEditFlags_AlphaPreviewHalf |
            ImGuiColorEditFlags_PickerHueWheel);
        if (ImGui::IsItemHovered())
            ImGui::SetTooltip("%s", visible_label(label).c_str());
        ImGui::PopID();
        return changed;
    }

    bool int_textbox(const std::string& label, int* value)
    {
        if (!value || !begin_field_row(label))
            return false;

        const bool changed = ImGui::DragInt(
            "##value", value, 1.0f, 1, (std::numeric_limits<int>::max)(), "%d", ImGuiSliderFlags_AlwaysClamp);
        end_field_row();
        return changed;
    }

    bool textbox(const std::string& label, std::string* value)
    {
        if (!value || !begin_field_row(label))
            return false;

        const bool changed = ImGui::InputText("##value", value);
        end_field_row();
        return changed;
    }

    bool checkbox(const std::string& label, bool* value)
    {
        if (!value || !begin_field_row(label))
            return false;

        const bool changed = toggle_button("value", value);
        end_field_row();
        return changed;
    }

    bool is_keybind_capturing() noexcept
    {
        return g_keybind_capture_id != 0;
    }

    void hint_text(const char* text)
    {
        ImGui::PushTextWrapPos(0.0f);
        ImGui::TextDisabled("%s", ta::presentation::localization::tr(text ? text : "").c_str());
        ImGui::PopTextWrapPos();
    }

    void warning_text(const char* text)
    {
        ImGui::TextWrapped("%s", ta::presentation::localization::tr(text ? text : "").c_str());
    }
}
