#pragma once

#include "ZoneProxy.hpp"
#include "interest_management/FixedGrid.hpp"
#include "network/SessionId.hpp"
#include "systems/Interest.hpp"
#include "systems/InterestSystem.hpp"
#include "world/JoltPhysicsWorld.hpp"
#include "world/World.hpp"

#include "PlayerMove.pb.h"

#include <entt/entt.hpp>
#include <glm/glm.hpp>
#include <memory>
#include <unordered_map>
#include <vector>

namespace tw::net {

using SpatialBackendType = im::FixedGrid<500, 500>;

static constexpr float kNeighborBorderOverlap = 100.0f;

/**
 * Manages one zone: contains state of all entities inside and does logic for them.
 */
class ZoneManager : public ZoneProxy {
    struct NeighborEntry {
        ZoneProxy*     proxy;
        im::InterestId interest_id;
    };

    std::unique_ptr<World>                                   m_world;
    JoltPhysicsWorld                                         m_physics_world;
    std::unique_ptr<SpatialBackendType>                      m_spatial_backend;
    std::unique_ptr<im::InterestSystem<SpatialBackendType>>  m_interest_system;

    std::unordered_map<im::InterestId, entt::entity>         m_client_entities;
    std::unordered_map<entt::entity, im::InterestId>         m_entity_sessions;
    std::unordered_map<im::InterestId, uint32_t>             m_session_input_frame;
    std::unordered_map<im::InterestId, uint32_t>             m_session_acked_frame;
    std::vector<NeighborEntry>                               m_neighbors;
    uint32_t                                                 m_next_neighbor_id = 0x80000000u;

    entt::entity client_entity(im::InterestId interest_id) const;
    void         check_neighbor_transfers();

public:
    entt::registry& registry() { return m_world->registry(); }

    im::InterestSystem<SpatialBackendType>* interest() const {
        return m_interest_system.get();
    }

    ZoneManager(im::AreaBounds zone_bounds);

    im::AreaBounds area() const override;

    void transfer_entity(EntityInfo&& info) override;

    entt::entity spawn_entity(EntityInfo&& info);

    void add_client(im::InterestId interest_id, entt::entity entity);

    void on_player_move(SessionId session_id, mmo::PlayerMoveMessage&& message);

    void register_neighbor_zone(ZoneProxy* neighbor);

    const im::Interest* get_interest(im::InterestId id) const;

    uint32_t acked_input_frame(im::InterestId interest_id) const;

    void update(uint32_t frame_idx, float delta_time);
};

} // namespace tw::net
