#pragma once

#include "cli/cli.hpp"
#include "components/camera.hpp"
#include "core/application.hpp"
#include "graphics/opengl/framebuffer.hpp"
#include "platform/glfw/glfw_window.hpp"
#include "renderer/gl_renderer.hpp"
#include "resources/resource_manager.hpp"

#include <flecs.h>

#include <expected>
#include <memory>
#include <system_error>
#include <vector>

namespace kuiper
{

struct engine_metrics {
    float                rendering_time = 0.0f;
    gl::renderer_metrics renderer_metrics {};
};

class engine {
  public:
    engine(std::unique_ptr<application>&& app);
    ~engine();
    engine(const engine& other)                = delete;
    engine& operator=(const engine& other)     = delete;
    engine(engine&& other) noexcept            = delete;
    engine& operator=(engine&& other) noexcept = delete;

    /// Initialise core engine subsystems
    static std::expected<void, std::error_code> initialise(const cli& cli_args) noexcept;

    void start();

  public:
    std::shared_ptr<glfw_window> window() noexcept {
        return m_window;
    }

    engine_metrics metrics() const noexcept {
        return m_metrics;
    }

    flecs::world& world() noexcept {
        return m_world;
    }

    resource_manager& resources() noexcept {
        return m_resources;
    }

    void add_render_target(const gl::framebuffer::s_ptr& target) noexcept;
    void remove_render_target(const gl::framebuffer::s_ptr& target) noexcept;
    /// Only draw to render targets added with `add_render_target()`
    void disable_default_framebuffer() noexcept;
    /// Draw to default framebuffer as well as any additional render targets
    void enable_default_framebuffer() noexcept;
    void set_render_camera(const glm::vec3& pos, const component::camera_props& cam_props) noexcept;

  private:
    std::unique_ptr<application> m_application {nullptr};
    std::shared_ptr<glfw_window> m_window {nullptr};
    engine_metrics               m_metrics {};

    std::vector<gl::framebuffer::s_ptr> m_render_targets {};
    bool                                m_render_to_default {true};
    glm::vec3                           m_cam_pos {};
    component::camera_props             m_cam_props {};

    resource_manager m_resources {};
    flecs::world     m_world {};
};

// Some assumptions that I make
static_assert(sizeof(char8_t) == sizeof(char));

} // namespace kuiper
