#pragma once

#include <entt/entt.hpp>
#include <glm/gtx/io.hpp>

#include "ProtobufMessages.hpp"
#include "debug/metrics/NetworkMetrics.hpp"
#include "entt/entity/fwd.hpp"
#include "io/InputState.hpp"
#include "metrics/HistoryBufferExporter.hpp"
#include "network/EntityIdMap.hpp"
#include "network/ReplicatorClient.hpp"
#include "runtime/LockStep.hpp"
#include "world/JoltPhysicsWorld.hpp"
#include "world/World.hpp"
#include "draw/WorldRenderer.hpp"
#include "world/ThirdPersonPlayerController.hpp"
#include "network/EntityPositionInterpolator.hpp"

namespace tw::net {
class ServerConnection;
}

namespace tw {

/**
 * Multiplayer player controller. Requires address of a server to work.
 */
class ClientWorldController {
    /**
     * Inputs
     */
    const io::InputManager* m_input_manager;

    /**
     * Pointers to the world
     */
    World* m_world;
    drw::WorldRenderer* m_world_renderer;
    JoltPhysicsWorld* m_physics_world;

    std::optional<entt::entity> m_player_entity;
    std::optional<uint32_t> m_controlled_server_id;
    ThirdPersonPlayerController m_player_controller;

    net::ServerConnection* m_connection;
    ProtobufMessages       m_messages;

    dbg::NetworkMetrics* m_network_metrics;

    LockStep m_input_update_tick_step;

    uint32_t m_frame_idx = 1;
    uint32_t m_network_frame_idx = 1;
    entt::entity m_entity_id;

    glm::vec3 m_input;

    std::optional<drw::Mesh> m_mesh;

    net::EntityPositionInterpolator m_interpolator;

    net::ReplicatorClient m_replicator_client;

    net::EntityIdMap m_entity_id_map;

    using Clock = std::chrono::steady_clock;

    void try_bind_player_entity();

    entt::entity create_entity(const std::string& name, glm::vec3 position);

    void apply_entity_positions(const mmo::EntityPosition* const* positions, size_t count);

    void update_network();

public:
    ClientWorldController(
            const io::InputManager* inputs,
            World* world,
            JoltPhysicsWorld* physics_world,
            drw::WorldRenderer* world_renderer,
            net::ServerConnection* connection,
            dbg::NetworkMetrics* network_metrics
    );

    ~ClientWorldController();


    constexpr uint32_t frame_idx() const {
        return m_frame_idx;
    }

    constexpr std::optional<entt::entity> player_entity() const {
        if(!m_controlled_server_id.has_value()) {
            return {};
        }

        return m_entity_id_map.get_local(m_controlled_server_id.value());
    }

    void spawn_entity(const std::string& name, uint32_t server_id);

    void update(double delta_time);
};

}
