#pragma once

#include <variant>
#include "./Area.hpp"
#include "./Voxel.hpp"
#include "../geometry/Ray.hpp"
#include "../geometry/Shapes.hpp"

namespace world {
class Elements;

/// Whole abstract universe container
class Universe {
public:
    virtual ~Universe() {}

    /// Distance management
    struct options {
        /// Radius in chunks to load if missing
        uint16_t loadDistance = 5;
        /// Radius in chunks to keep in memory
        uint16_t keepDistance = 6;
    };

    /// Universe element ray intersection
    using element_query_target = relative_pos;
    using element_query_result = std::optional<element_query_target>;
    /// Universe voxel ray intersection
    struct area_query_target {
        area_query_target(const area_voxel_pos& pos, Voxel value):
            pos(pos), value(value) { }
        area_voxel_pos pos;
        Voxel value;
    };
    /// Universe unloaded chunk ray intersection
    using area_query_out_of_range = area_chunk_pos;
    using area_query_result = std::variant<std::monostate, area_query_target, area_query_out_of_range>;

    struct voxel_query_target {
        voxel_query_target(const node_voxel_pos& pos, Voxel value):
            pos(pos), value(value) { }
        node_voxel_pos pos;
        Voxel value;
    };
    using query_result = std::variant<std::monostate, voxel_query_target, element_query_target, area_query_out_of_range>;

    /// Get nearest element colliding ray
    static element_query_result Raycast(const Elements&, const geometry::Ray &ray, const node_id& ignore = generational::id());
    /// Get nearest area voxel colliding ray
    static area_query_result RaycastArea(const Elements&, const geometry::Ray &ray, bool solid_only = true);
    /// Get nearest thing colliding ray
    static query_result Raycast(const Elements&, const geometry::Ray &ray, bool solid_only, const node_id& ignore = generational::id());
    /// Get first element colliding point
    static element_query_result At(const Elements&, const world_pos &point, const node_id& ignore = generational::id());
    /// Get first thing colliding point
    static query_result At(const Elements&, const world_pos &point, bool solid_only, const node_id& ignore = generational::id());
    /// Check for entities in range
    static bool IsRangeFree(const Elements&, const node_voxel_ref &center, const geometry::Volume&);
};

}