#pragma once

#include "Interest.hpp"
#include "interest_management/SpatialBackend.hpp"
#include "world/Transform.hpp"
#include "world/World.hpp"

#include <algorithm>
#include <entt/entt.hpp>
#include <tracy/Tracy.hpp>
#include <unordered_map>

namespace tw::net::im {

template<SpatialBackend Backend>
class InterestSystem {
    const World*                             m_world;
    Backend*                                 m_backend;
    std::unordered_map<InterestId, Interest> m_interests;

public:
    explicit InterestSystem(const World* world, Backend* backend)
        : m_world(world),
          m_backend(backend)
    {}

    void set_entity_interest(InterestId id, entt::entity entity) {
        m_interests.insert_or_assign(id, Interest(entity));
    }

    void set_area_interest(InterestId id, AreaBounds bounds) {
        m_interests.insert_or_assign(id, Interest(bounds));
    }

    void remove_interest(InterestId id) {
        m_interests.erase(id);
    }

    const Interest* get_interest(InterestId id) const {
        auto it = m_interests.find(id);
        return it != m_interests.end() ? &it->second : nullptr;
    }

    void update() {
        ZoneScopedN("InterestSystem::update");

        {
            ZoneScopedN("InterestSystem::Phase1_GridRebuild");
            m_backend->begin_frame();
            m_world->registry()
                .view<Transform>()
                .each([this](entt::entity e, const Transform& t) {
                    m_backend->insert(e, t.position());
                });
        }

        {
            ZoneScopedN("InterestSystem::Phase2_Deltas");

            for (auto& [id, interest] : m_interests) {
                interest.new_interest().clear();

                if (interest.is_entity_interest()) {
                    entt::entity entity = interest.entity();
                    if (entity == entt::null) {
                        interest.spawn().clear();
                        interest.despawn().clear();
                        continue;
                    }
                    const Transform* t = m_world->registry().try_get<Transform>(entity);
                    if (!t) {
                        interest.spawn().clear();
                        interest.despawn().clear();
                        std::vector<entt::entity> empty;
                        interest.swap_interest(empty);
                        continue;
                    }
                    m_backend->query_neighbors(t->position(), interest.new_interest());
                } else {
                    const auto& b = interest.bounds();
                    m_backend->query_area(b.min, b.max, interest.new_interest());
                }

                std::sort(interest.new_interest().begin(), interest.new_interest().end());

                interest.spawn().clear();
                std::set_difference(
                    interest.new_interest().begin(), interest.new_interest().end(),
                    interest.interest().begin(),     interest.interest().end(),
                    std::back_inserter(interest.spawn())
                );

                interest.despawn().clear();
                std::set_difference(
                    interest.interest().begin(),     interest.interest().end(),
                    interest.new_interest().begin(), interest.new_interest().end(),
                    std::back_inserter(interest.despawn())
                );

                interest.swap_interest(interest.new_interest());
            }
        }
    }
};

} // namespace tw::net::im
