#include "presentation/pages/quests/QuestSelectionModel.h"
#include "foundation/text/StringUtils.h"

#include <algorithm>
#include <cctype>
#include <iterator>

namespace
{

    bool category_matches(const ta::features::quests::contracts::QuestEntry& quest, const int filter)
    {
        using ta::features::quests::contracts::QuestCategory;
        switch (filter)
        {
        case 0: return quest.category == QuestCategory::Main;
        case 1: return quest.category == QuestCategory::Side;
        case 2: return quest.category == QuestCategory::Operational;
        case 3: return quest.category == QuestCategory::Unclassified;
        default: return false;
        }
    }
}

namespace ta::presentation::pages::quests
{
    void QuestSelectionModel::refresh(std::shared_ptr<const Snapshot> snapshot)
    {
        if (snapshot_ != snapshot)
        {
            snapshot_ = std::move(snapshot);
            prune_selection();
            filtered_snapshot_.reset();
        }
        rebuild_filter();
        ensure_visible_detail();
    }

    bool QuestSelectionModel::selected(const Entry& quest) const
    {
        return selected_ids_.contains(quest.id);
    }

    void QuestSelectionModel::set_selected(const Entry& quest, const bool selected_value)
    {
        if (selected_value)
        {
            selected_ids_.insert(quest.id);
            selected_condition_ids_.erase(quest.id);
        }
        else
        {
            selected_ids_.erase(quest.id);
        }
    }

    void QuestSelectionModel::clear_selection() noexcept
    {
        selected_ids_.clear();
        selected_condition_ids_.clear();
    }

    std::size_t QuestSelectionModel::selected_count() const noexcept
    {
        return selected_ids_.size();
    }

    const std::set<std::string>& QuestSelectionModel::selected_ids() const noexcept
    {
        return selected_ids_;
    }

    bool QuestSelectionModel::condition_selected(const Entry& quest, const Condition& condition) const
    {
        const auto quest_it = selected_condition_ids_.find(quest.id);
        return quest_it != selected_condition_ids_.end() && quest_it->second.contains(condition.id);
    }

    void QuestSelectionModel::set_condition_selected(const Entry& quest,
                                                      const Condition& condition,
                                                      const bool selected_value)
    {
        if (condition.id.empty() || condition.completed)
            return;

        if (selected_value)
        {
            selected_ids_.erase(quest.id);
            selected_condition_ids_[quest.id].insert(condition.id);
            return;
        }

        const auto quest_it = selected_condition_ids_.find(quest.id);
        if (quest_it == selected_condition_ids_.end())
            return;
        quest_it->second.erase(condition.id);
        if (quest_it->second.empty())
            selected_condition_ids_.erase(quest_it);
    }

    void QuestSelectionModel::select_incomplete_conditions(const Entry& quest)
    {
        selected_ids_.erase(quest.id);
        auto& selected = selected_condition_ids_[quest.id];
        for (const Condition& condition : quest.conditions)
        {
            if (!condition.completed && !condition.id.empty())
                selected.insert(condition.id);
        }
        if (selected.empty())
            selected_condition_ids_.erase(quest.id);
    }

    void QuestSelectionModel::clear_condition_selection(const Entry& quest)
    {
        selected_condition_ids_.erase(quest.id);
    }

    std::size_t QuestSelectionModel::selected_condition_count() const noexcept
    {
        std::size_t count = 0;
        for (const auto& [_, conditions] : selected_condition_ids_)
            count += conditions.size();
        return count;
    }

    std::size_t QuestSelectionModel::selected_condition_count(const Entry& quest) const noexcept
    {
        const auto found = selected_condition_ids_.find(quest.id);
        return found == selected_condition_ids_.end() ? 0U : found->second.size();
    }

    std::size_t QuestSelectionModel::total_selection_count() const noexcept
    {
        return selected_count() + selected_condition_count();
    }

    const QuestSelectionModel::ConditionSelection& QuestSelectionModel::selected_condition_ids() const noexcept
    {
        return selected_condition_ids_;
    }

    void QuestSelectionModel::select_visible(const bool selected_value)
    {
        for (const Entry* quest : visible_)
        {
            if (quest)
                set_selected(*quest, selected_value);
        }
    }


    const std::vector<const QuestSelectionModel::Entry*>& QuestSelectionModel::visible() const noexcept
    {
        return visible_;
    }

    void QuestSelectionModel::select_detail(const std::string& id)
    {
        selected_detail_id_ = id;
    }

    const std::string& QuestSelectionModel::selected_detail_id() const noexcept
    {
        return selected_detail_id_;
    }

    const QuestSelectionModel::Entry* QuestSelectionModel::selected_detail() const noexcept
    {
        if (!snapshot_ || selected_detail_id_.empty())
            return nullptr;
        const auto found = std::find_if(snapshot_->quests.begin(), snapshot_->quests.end(), [&](const Entry& quest)
        {
            return quest.id == selected_detail_id_;
        });
        return found == snapshot_->quests.end() ? nullptr : &*found;
    }

    void QuestSelectionModel::prune_selection()
    {
        if (!snapshot_)
        {
            selected_ids_.clear();
            selected_condition_ids_.clear();
            selected_detail_id_.clear();
            return;
        }

        for (auto iterator = selected_ids_.begin(); iterator != selected_ids_.end();)
        {
            const bool exists = std::ranges::any_of(snapshot_->quests, [&](const Entry& quest)
            {
                return quest.id == *iterator;
            });
            iterator = exists ? std::next(iterator) : selected_ids_.erase(iterator);
        }

        for (auto quest_it = selected_condition_ids_.begin(); quest_it != selected_condition_ids_.end();)
        {
            const auto quest = std::ranges::find_if(snapshot_->quests, [&](const Entry& entry)
            {
                return entry.id == quest_it->first;
            });
            if (quest == snapshot_->quests.end())
            {
                quest_it = selected_condition_ids_.erase(quest_it);
                continue;
            }

            for (auto condition_it = quest_it->second.begin(); condition_it != quest_it->second.end();)
            {
                const bool exists = std::ranges::any_of(quest->conditions, [&](const Condition& condition)
                {
                    return condition.id == *condition_it && !condition.completed;
                });
                condition_it = exists ? std::next(condition_it) : quest_it->second.erase(condition_it);
            }

            if (quest_it->second.empty())
                quest_it = selected_condition_ids_.erase(quest_it);
            else
                ++quest_it;
        }

        if (!selected_detail_id_.empty() &&
            !std::ranges::any_of(snapshot_->quests, [&](const Entry& quest)
            {
                return quest.id == selected_detail_id_;
            }))
        {
            selected_detail_id_.clear();
        }
    }

    bool QuestSelectionModel::matches(const Entry& quest) const
    {
        return category_matches(quest, category_filter_);
    }

    void QuestSelectionModel::rebuild_filter()
    {
        if (filtered_snapshot_ == snapshot_ &&
            filtered_category_ == category_filter_)
        {
            return;
        }

        filtered_snapshot_ = snapshot_;
        filtered_category_ = category_filter_;
        visible_.clear();

        if (!snapshot_)
            return;

        visible_.reserve(snapshot_->quests.size());
        for (const Entry& quest : snapshot_->quests)
        {
            if (matches(quest))
                visible_.push_back(&quest);
        }

        std::ranges::stable_sort(visible_, [](const Entry* left, const Entry* right)
        {
            if (!left || !right)
                return right != nullptr;
            const std::string left_trader = ta::foundation::text::lower_copy(left->trader_name);
            const std::string right_trader = ta::foundation::text::lower_copy(right->trader_name);
            if (left_trader != right_trader)
                return left_trader < right_trader;
            return ta::foundation::text::lower_copy(left->name) < ta::foundation::text::lower_copy(right->name);
        });
    }

    void QuestSelectionModel::ensure_visible_detail()
    {
        const bool visible = std::ranges::any_of(visible_, [&](const Entry* quest)
        {
            return quest && quest->id == selected_detail_id_;
        });
        if (!visible)
            selected_detail_id_ = visible_.empty() ? std::string{} : visible_.front()->id;
    }
}
