#pragma once
#include "Renderable.h"
#include <renderive/base/property/Concepts.hpp>
#include <renderive/base/property/Validators.hpp>
#include <renderive/scene/base/Scene_Base.hpp>
#include <algorithm>
#include <concepts>
#include <functional>
#include <memory>
#include <utility>
#include <vector>
namespace renderive {
class Abs_Axis;
namespace detail {
class Paint_Overlay;

template <class Renderable_Type>
void append_unique(std::vector<::Scene_Base::Renderable>& renderables,
                   const std::shared_ptr<Renderable_Type>& renderable) {
    ::Scene_Base::Renderable base = renderable;
    if (base && std::find(renderables.begin(), renderables.end(), base) ==
                    renderables.end())
        renderables.push_back(std::move(base));
}

template <class Renderable_Type, class Axis_Type>
requires std::derived_from<Renderable_Type, Renderable> &&
         std::derived_from<Axis_Type, Abs_Axis>
void append_renderable_dependency(
    ::Scene_Base::Attach_Relationships& relationships,
    const std::shared_ptr<Axis_Type>& axis) {
    if (!axis)
        return;
    append_unique(relationships.dependency_parents, axis);
    if constexpr (std::derived_from<Renderable_Type, Paint_Overlay>)
        append_unique(relationships.display_parents, axis);
    else
        append_unique(relationships.display_children, axis);
}

template <class Renderable_Type, class Value>
void append_renderable_dependency(
    ::Scene_Base::Attach_Relationships&, const Value&) {}

template <class Control, class... Args>
requires std::derived_from<Control, Renderable>
auto renderable_attach_relationships(
    const std::shared_ptr<Renderable>& parent, const Args&... args)
    -> ::Scene_Base::Attach_Relationships {
    ::Scene_Base::Attach_Relationships relationships;
    append_unique(relationships.display_parents, parent);
    append_unique(relationships.dependency_parents, parent);
    (append_renderable_dependency<Control>(relationships, args), ...);
    return relationships;
}
}

template <Property_Product Product_Type, Property_Set Properties_Type, Property_Validator<Properties_Type> Validator_Type = No_Property_Validator<Properties_Type>>
class Renderable_Builder {
public:
    using Product = Product_Type;
    using Properties = Properties_Type;
    using Validator = Validator_Type;
    using Self = Renderable_Builder;
    Renderable_Builder() requires std::default_initializable<Properties> && std::default_initializable<Validator> : properties{}, validator{} {}
    explicit Renderable_Builder(Properties properties) requires std::default_initializable<Validator> : properties(std::move(properties)), validator{} {}
    Renderable_Builder(Properties properties, Validator validator) : properties(std::move(properties)), validator(std::move(validator)) {}
    template <auto Member, Property_Member_Assignable<Properties, Member> Value>
    Self& set(Value&& value) {
        properties.*Member = std::forward<Value>(value);
        return *this;
    }
    template <Property_Configurator<Properties> Configure>
    Self& configure(Configure&& configure) {
        std::invoke(std::forward<Configure>(configure), properties);
        return *this;
    }
    const Properties& properties_value() const noexcept {
        return properties;
    }
    template <class... Args>
    requires std::derived_from<Product, Renderable> && std::constructible_from<Product, ::Scene_Base&, const Properties&, Args&&...>
    std::shared_ptr<Product> build(const std::shared_ptr<Renderable>& parent, Args&&... args) const {
        if (!parent || !(valid_argument(args) && ...))
            return {};
        validator(properties);
        auto relationships =
            detail::renderable_attach_relationships<Product>(parent, args...);
        auto result = parent->scene().template make_renderable<Product>(properties, std::forward<Args>(args)...);
        parent->scene().attach_renderable(result, std::move(relationships));
        return result;
    }
private:
    template <class T>
    static bool valid_argument(const std::shared_ptr<T>& value) {
        return static_cast<bool>(value);
    }
    template <class T>
    static bool valid_argument(const T&) {
        return true;
    }
    Properties properties;
    [[no_unique_address]] Validator validator;
};
}
