#include "presentation/action_feedback/ActionFeedbackPresenter.h"

#include <algorithm>
#include <cstddef>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include <imgui.h>

#include "foundation/diagnostics/ActionOutcome.h"
#include "foundation/diagnostics/ExecutionPath.h"
#include "presentation/localization/UiLocalizationService.h"
#include "presentation/runtime/Viewport.h"
#include "presentation/theme/MenuTheme.h"

namespace ta::presentation
{
    namespace
    {
        class ActionFeedbackModel final
        {
        public:
            struct ActionState final
            {
                std::string label;
                ActionFeedbackPresenter::Status status = ActionFeedbackPresenter::Status::Idle;
                std::uint64_t trace_id = 0;
                double changed_at = 0.0;
                std::string detail;
            };

            struct Toast final
            {
                std::string key;
                std::uint64_t trace_id = 0;
            };

            void begin(std::string key,
                       std::string label,
                       const std::uint64_t trace_id,
                       const double now)
            {
                ActionState& action = actions_[key];
                if (action.trace_id != 0)
                    trace_keys_.erase(action.trace_id);

                action.label = std::move(label);
                action.status = ActionFeedbackPresenter::Status::Pending;
                action.trace_id = trace_id;
                action.changed_at = now;
                action.detail.clear();
                trace_keys_[trace_id] = key;

                const auto toast = std::find_if(toasts_.begin(), toasts_.end(),
                    [&](const Toast& candidate) { return candidate.key == key; });
                if (toast == toasts_.end())
                    toasts_.push_back({std::move(key), trace_id});
                else
                    toast->trace_id = trace_id;
            }

            void consume(const ta::foundation::action_outcomes::ActionOutcome& outcome,
                         const double now)
            {
                const auto key_it = trace_keys_.find(outcome.trace_id);
                if (key_it == trace_keys_.end())
                    return;

                const auto action_it = actions_.find(key_it->second);
                if (action_it == actions_.end() || action_it->second.trace_id != outcome.trace_id)
                    return;

                ActionState& action = action_it->second;
                action.status = outcome.stage == ta::foundation::action_outcomes::Stage::Succeeded
                    ? ActionFeedbackPresenter::Status::Success
                    : ActionFeedbackPresenter::Status::Failed;
                action.changed_at = now;
                action.detail = outcome.detail;
                trace_keys_.erase(key_it);
            }

            void update(const double now)
            {
                std::erase_if(toasts_, [&](const Toast& toast)
                {
                    const auto found = actions_.find(toast.key);
                    if (found == actions_.end() || found->second.trace_id != toast.trace_id)
                        return true;
                    return found->second.status != ActionFeedbackPresenter::Status::Pending &&
                        now - found->second.changed_at > 5.0;
                });
            }

            [[nodiscard]] ActionFeedbackPresenter::Status status(
                const std::string& key,
                const double now) const
            {
                const auto found = actions_.find(key);
                if (found == actions_.end())
                    return ActionFeedbackPresenter::Status::Idle;

                const ActionState& action = found->second;
                if (action.status != ActionFeedbackPresenter::Status::Pending &&
                    action.status != ActionFeedbackPresenter::Status::Idle &&
                    now - action.changed_at > 8.0)
                {
                    return ActionFeedbackPresenter::Status::Idle;
                }
                return action.status;
            }

            [[nodiscard]] const ActionState* find(const std::string& key) const noexcept
            {
                const auto found = actions_.find(key);
                return found == actions_.end() ? nullptr : &found->second;
            }

            [[nodiscard]] const std::vector<Toast>& toasts() const noexcept
            {
                return toasts_;
            }

            void clear() noexcept
            {
                actions_.clear();
                trace_keys_.clear();
                toasts_.clear();
            }

        private:
            std::unordered_map<std::string, ActionState> actions_;
            std::unordered_map<std::uint64_t, std::string> trace_keys_;
            std::vector<Toast> toasts_;
        };

        ActionFeedbackModel& model()
        {
            static ActionFeedbackModel instance;
            return instance;
        }

        ImVec4 toast_color(const ActionFeedbackPresenter::Status status)
        {
            switch (status)
            {
            case ActionFeedbackPresenter::Status::Success:
                return ta::presentation::theme::palette::success;
            case ActionFeedbackPresenter::Status::Failed:
                return ImVec4(0.95f, 0.35f, 0.35f, 1.0f);
            case ActionFeedbackPresenter::Status::Pending:
                return ta::presentation::theme::palette::accent;
            case ActionFeedbackPresenter::Status::Idle:
            default:
                return ta::presentation::theme::palette::muted;
            }
        }

        const char* status_label(const ActionFeedbackPresenter::Status status)
        {
            switch (status)
            {
            case ActionFeedbackPresenter::Status::Pending: return "Queued";
            case ActionFeedbackPresenter::Status::Success: return "Success";
            case ActionFeedbackPresenter::Status::Failed: return "Failed";
            case ActionFeedbackPresenter::Status::Idle:
            default: return "Idle";
            }
        }
    }

    std::uint64_t ActionFeedbackPresenter::begin(const std::string& key,
                                                 const std::string& label,
                                                 const std::string& feature,
                                                 const std::string& detail)
    {
        const std::uint64_t trace_id = ta::foundation::execution_path::begin(feature, label, detail);
        model().begin(key, label, trace_id, ImGui::GetTime());
        return trace_id;
    }

    void ActionFeedbackPresenter::update()
    {
        const double now = ImGui::GetTime();
        for (const auto& outcome : ta::foundation::action_outcomes::drain())
            model().consume(outcome, now);
        model().update(now);
    }

    void ActionFeedbackPresenter::draw_toasts()
    {
        const auto& toasts = model().toasts();
        if (toasts.empty())
            return;

        const float scale = ta::presentation::runtime::scale_factor();
        const ImGuiViewport* viewport = ImGui::GetMainViewport();
        if (!viewport)
            return;

        ImGui::SetNextWindowPos(
            ImVec2(viewport->WorkPos.x + viewport->WorkSize.x - 18.0f * scale,
                   viewport->WorkPos.y + viewport->WorkSize.y - 18.0f * scale),
            ImGuiCond_Always,
            ImVec2(1.0f, 1.0f));
        ImGui::SetNextWindowBgAlpha(0.94f);

        constexpr ImGuiWindowFlags flags =
            ImGuiWindowFlags_NoDecoration |
            ImGuiWindowFlags_AlwaysAutoResize |
            ImGuiWindowFlags_NoSavedSettings |
            ImGuiWindowFlags_NoFocusOnAppearing |
            ImGuiWindowFlags_NoNav |
            ImGuiWindowFlags_NoMove;

        if (!ImGui::Begin("##action_feedback_toasts", nullptr, flags))
        {
            ImGui::End();
            return;
        }

        const std::size_t first = toasts.size() > 4 ? toasts.size() - 4 : 0;
        for (std::size_t i = first; i < toasts.size(); ++i)
        {
            const ActionFeedbackModel::Toast& toast = toasts[i];
            const ActionFeedbackModel::ActionState* action = model().find(toast.key);
            if (!action || action->trace_id != toast.trace_id)
                continue;

            ImGui::PushID(static_cast<int>(i));
            ImGui::TextColored(toast_color(action->status), "%s",
                               ta::presentation::localization::tr(status_label(action->status)).c_str());
            ImGui::SameLine();
            ImGui::TextUnformatted(action->label.c_str());
            if (!action->detail.empty())
            {
                ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + 320.0f * scale);
                ImGui::TextColored(ta::presentation::theme::palette::muted, "%s", action->detail.c_str());
                ImGui::PopTextWrapPos();
            }
            if (i + 1 < toasts.size())
                ImGui::Separator();
            ImGui::PopID();
        }

        ImGui::End();
    }

    void ActionFeedbackPresenter::reset() noexcept
    {
        model().clear();
        ta::foundation::action_outcomes::clear();
    }

    ActionFeedbackPresenter::Status ActionFeedbackPresenter::status(const std::string& key)
    {
        return model().status(key, ImGui::GetTime());
    }

    bool ActionFeedbackPresenter::is_pending(const std::string& key)
    {
        return status(key) == Status::Pending;
    }

    std::string ActionFeedbackPresenter::status_text(const std::string& key)
    {
        const Status current = status(key);
        const ActionFeedbackModel::ActionState* action = model().find(key);
        if (!action || current == Status::Idle)
            return {};

        std::string text = ta::presentation::localization::tr(status_label(current));
        if (!action->detail.empty())
            text += ": " + action->detail;
        return text;
    }

    const char* ActionFeedbackPresenter::item_action_key(
        const ta::features::items::contracts::PendingAction action) noexcept
    {
        switch (action)
        {
        case ta::features::items::contracts::PendingAction::CreateItem: return "item:create";
        case ta::features::items::contracts::PendingAction::TransitItem: return "item:transit";
        default: return "item:action";
        }
    }

    const char* ActionFeedbackPresenter::quest_action_key(
        const ta::features::quests::contracts::QuestAction action) noexcept
    {
        return action == ta::features::quests::contracts::QuestAction::SkipTask
            ? "quest:skip"
            : "quest:complete";
    }
}
