#pragma once

#include "core/world/Area.hpp"
#include "core/geometry/Frustum.hpp"
#include "core/geometry/Ray.hpp"
#include "core/geometry/Faces.hpp"
#include "core/utils/flags.hpp"
#include <glm/gtc/matrix_transform.hpp>

namespace render {
    class Model;
    class LodModel;
}
/// Mesh creation (from world to render)
namespace contouring {
    /// Generating mesh from world data
    class Abstract {
    public:
        Abstract() { }
        virtual ~Abstract() { }

        /// Each frame ping.
        /// Mostly used for cleanup and to flush buffers data using main thread
        virtual void update(const world::cell_pos &pos, const world::Elements &) = 0;

        /// Chunk data change
        /// @param offset priority position offset
        virtual void onUpdate(const world::area_chunk_pos &pos, glm::ll offset, const world::ChunkContainer &data, geometry::Faces neighbors) = 0;
        /// Chunk existante ping
        /// @note notify for chunks entering view while moving
        /// @param offset priority position offset
        virtual void onNotify(const world::area_chunk_pos &pos, glm::ll offset, const world::ChunkContainer &data) = 0;

        /// Part chunk data change
        /// @param offset priority position offset
        virtual void onNotify(const world::part_id&, glm::ll offset, const glm::ucvec3& size, const std::vector<std::shared_ptr<world::EdittableChunk>>&, bool update) = 0;

        /// Register model
        virtual void onLoad(const world::model_id&, std::istream&) = 0;
        /// Free model
        virtual void onUnload(const world::model_id&) = 0;

        /// Display ImGui config
        virtual void onGui() = 0;
        /// Get options
        virtual std::string getOptions() const = 0;
        /// Get camera recommended far_dist range
        virtual std::pair<float, float> getFarRange() const = 0;

        /// Get pending elements
        virtual size_t getQueueSize() = 0;

        /// Get max visible distance in chunks
        virtual uint16_t getVisibleDist() const = 0;

        struct area_info {
            world::transform absolute;
            world::cell_pos::value_type radius;
            float curvature;
        };
        using terrain_draw_call = const std::function<void(const glm::mat4&, render::LodModel *const, const area_info&, const world::voxel_pos& chunk_area_offset)> &;
        using unique_draw_call = const std::function<void(const glm::mat4&, render::Model *const)> &;
        using instanced_draw_call = const std::function<void(const std::vector<glm::mat4>&, render::Model *const)> &;

        /// Get buffers in frustum with model matrices
        /// @note buffers invalidated after update
        virtual void getTerrainModels(terrain_draw_call draw, const std::optional<geometry::Frustum>& frustum, const world::cell_pos& offset, bool solid) const = 0;
        /// Get buffers hitting occlusion rays with model matrices
        /// @note buffers invalidated after update
        virtual void getTerrainModels(terrain_draw_call draw, const world::world_pos &from, float far_dist, const std::vector<glm::vec3> &occlusion, const world::cell_pos&offset, bool solid) const = 0;

        /// Get buffer corresponding to part idx
        virtual render::Model* getModel(const world::part_id&) const = 0;
        /// Get buffers in frustum with model matrices
        /// @note buffers invalidated after update
        virtual void getUniqueModels(unique_draw_call draw, const world::Elements&, const std::optional<geometry::Frustum>& frustum, const world::cell_pos& offset) const = 0;

        /// Get buffer corresponding to model idx
        virtual render::Model* getModel(const world::model_id&) const = 0;
        /// Get buffers in frustum with model matrices
        /// @note buffers invalidated after update
        virtual void getInstancedModels(instanced_draw_call draw, const world::Elements&, const std::optional<geometry::Frustum>& frustum, const world::cell_pos& offset, world::node_id ignore) const = 0;
    };
}