#pragma once
#include <algorithm>
#include <cctype>
#include <functional>
#include <string>
#include <string.h>
#include <vector>

#include "ConfigValue.h"
#include "../assets/item_database.h"
#include "../gui/menu/imgui_addons.h"
#include "../util/i18n.h"

/**
 * Search box with autocomplete over the embedded Tarkov item database.
 * Type a (French or English) name, click a result -> the selected BSG id is
 * stored and pushed to a target via the on_select callback.
 */
#pragma pack(push, 1)
class ItemSearchValue : public ConfigValue
{
public:
    explicit ItemSearchValue(const std::string& n,
                             std::function<void(const std::string&)> on_select = nullptr)
        : ConfigValue(n)
    {
        this->on_select_ = std::move(on_select);
    }

    void draw() override
    {
        build_index();

        const std::string title = this->get_imgui_title();
        ImGui::PushID(title.c_str());

        // label
        std::string just_text = title.substr(0, title.find("##"));
        ImGui::Text("%s", just_text.c_str());

        // full-width search input
        ImGui::SetNextItemWidth(-1.f);
        if (ImGui::InputTextWithHint("##itemsearch", i18n::tr("item_search_hint_inf").c_str(), &query_))
        {
            refilter();
        }

        // current selection summary
        if (!selected_id_.empty())
        {
            ImGui::TextColored(ImGui::GetStyle().Colors[ImGuiCol_CheckMark],
                               "> %s  [%s]", selected_name_.c_str(), selected_id_.c_str());
        }

        // results list (only while there is a query)
        if (!query_.empty() && !results_.empty())
        {
            const float row_h = ImGui::GetTextLineHeightWithSpacing();
            const float height = (std::min)(static_cast<int>(results_.size()), 8) * row_h + 8.f;

            ImGui::BeginChild("##itemresults", ImVec2(-1.f, height), true);
            for (int idx : results_)
            {
                const ItemDef& it = ITEM_DB[idx];
                ImGui::PushID(idx);

                // English name as primary label (fallback to FR if EN is missing)
                std::string label = std::string(it.en[0] ? it.en : it.fr);

                if (ImGui::Selectable(label.c_str(), selected_id_ == it.id))
                {
                    selected_id_ = it.id;
                    selected_name_ = it.en[0] ? it.en : it.fr;
                    if (on_select_) on_select_(selected_id_);
                }
                ImGui::PopID();
            }
            ImGui::EndChild();

            ImGui::TextDisabled("%d result%s%s", static_cast<int>(results_.size()),
                                results_.size() > 1 ? "s" : "",
                                results_.size() >= MAX_RESULTS ? " (refine your search)" : "");
        }
        else if (!query_.empty())
        {
            ImGui::TextDisabled("%s", i18n::tr("item_search_no_results_inf").c_str());
        }

        ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetStyle().FramePadding.y * 2 * menu::get_scale_factor());
        ImGui::Dummy({0, 0});
        ImGui::PopID();
    }

    // persisted value = the selected id
    void set(const std::string& string) override
    {
        selected_id_ = string;
        // try to recover the display name from the db
        build_index();
        selected_name_.clear();
        for (int i = 0; i < ITEM_DB_COUNT; ++i)
        {
            if (selected_id_ == ITEM_DB[i].id) { selected_name_ = ITEM_DB[i].en[0] ? ITEM_DB[i].en : ITEM_DB[i].fr; break; }
        }
    }

    std::string get() override { return selected_id_; }
    std::string get_default() override { return ""; }

    [[nodiscard]] std::string get_selected_id() const { return selected_id_; }

private:
    static constexpr int MAX_RESULTS = 40;

    std::string query_;
    std::string selected_id_;
    std::string selected_name_;
    std::function<void(const std::string&)> on_select_;

    std::vector<int> results_;

    // lowercased "fr en id" haystack per item, built once
    static std::vector<std::string>& haystacks()
    {
        static std::vector<std::string> hs;
        return hs;
    }

    static void build_index()
    {
        auto& hs = haystacks();
        if (!hs.empty()) return;
        hs.reserve(ITEM_DB_COUNT);
        for (int i = 0; i < ITEM_DB_COUNT; ++i)
        {
            std::string h = std::string(ITEM_DB[i].fr) + ' ' + ITEM_DB[i].en + ' ' + ITEM_DB[i].id;
            std::transform(h.begin(), h.end(), h.begin(),
                           [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
            hs.push_back(std::move(h));
        }
    }

    void refilter()
    {
        results_.clear();
        std::string q = query_;
        std::transform(q.begin(), q.end(), q.begin(),
                       [](unsigned char c) { return static_cast<char>(std::tolower(c)); });

        // split into terms
        std::vector<std::string> terms;
        size_t start = 0;
        while (start < q.size())
        {
            size_t sp = q.find(' ', start);
            if (sp == std::string::npos) { terms.push_back(q.substr(start)); break; }
            if (sp > start) terms.push_back(q.substr(start, sp - start));
            start = sp + 1;
        }
        if (terms.empty()) return;

        const auto& hs = haystacks();
        for (int i = 0; i < ITEM_DB_COUNT && static_cast<int>(results_.size()) < MAX_RESULTS; ++i)
        {
            bool ok = true;
            for (const auto& t : terms)
            {
                if (hs[i].find(t) == std::string::npos) { ok = false; break; }
            }
            if (ok) results_.push_back(i);
        }

        // prefer items whose FR name starts with the first term
        const std::string& t0 = terms[0];
        std::stable_sort(results_.begin(), results_.end(), [&](int a, int b)
        {
            const bool sa = _strnicmp(ITEM_DB[a].fr, t0.c_str(), t0.size()) == 0;
            const bool sb = _strnicmp(ITEM_DB[b].fr, t0.c_str(), t0.size()) == 0;
            if (sa != sb) return sa;
            return false;
        });
    }
};
#pragma pack(pop)
