#pragma once

#include "../base/Types.h"
#include "../event/Event.h"

#include <renderive/renderable/base/Renderable_Base.hpp>
#include <renderive/renderable/base/Renderive_Owner.hpp>
#include <renderive/renderable/Renderable_Graph_Builder.hpp>

#include <atomic>
#include <concepts>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <type_traits>

namespace renderive {

class Renderable;
namespace detail {
class Painter;
class Renderable_State_Observer;
}

class Event_Handler {
public:
    virtual ~Event_Handler() = default;
    virtual void handle_event(const Event& event) = 0;
};

enum class Renderable_Observer_Event {
    None,
    Cache_Updated,
    Published
};

struct Renderable_Observation {
    Renderable_Observer_Event event{Renderable_Observer_Event::None};
    std::uint64_t event_time_ns{};
    std::uint64_t cache_update_count{};
    std::uint64_t publish_count{};
};

class LIB_DECL Renderable : public ::Renderable_Base {
public:
    explicit Renderable(bool cache_enabled = true);
    ~Renderable() override;

    [[nodiscard]] Renderable_Cache_Mode get_cache_mode() const noexcept;
    void set_cache_mode(Renderable_Cache_Mode mode);

    [[nodiscard]] std::string object_name() const;
    void set_object_name(std::string name);
    [[nodiscard]] Renderable_Observation observation() const noexcept;

protected:
    class Impl;

    template <class Implementation>
    explicit Renderable(std::unique_ptr<Implementation> implementation,
                        bool cache_enabled)
        : ::Renderable_Base(
              std::move(implementation),
              {.cache_enabled = cache_enabled}) {}

    using Prepare_Task_Function =
        std::function<void(const Prepare_Render_Context&)>;
    using Paint_Task_Function =
        std::function<void(detail::Painter&, const Paint_Render_Context&)>;

    virtual void prepare_frame(const Prepare_Render_Context& context) = 0;
    virtual void paint(detail::Painter& painter,
                       const Paint_Render_Context& context) = 0;
    virtual void build_prepare_graph(Renderable_Graph_Builder& builder);
    virtual void build_paint_graph(Renderable_Graph_Builder& builder);
    Renderable_Graph_Builder::Task add_prepare_task(Renderable_Graph_Builder& builder,
                                                    std::string logical_key,
                                                    std::string name,
                                                    Prepare_Task_Function function);
    Renderable_Graph_Builder::Task add_paint_task(Renderable_Graph_Builder& builder,
                                                  std::string logical_key,
                                                  std::string name,
                                                  Paint_Task_Function function);
    void changed() noexcept;
    void paint_changed() noexcept;
    void render_graph_changed();

private:
    void observe_state(Renderable_Observer_Event event, std::uint64_t time_ns,
                       std::uint64_t cache_update_count,
                       std::uint64_t publish_count) noexcept;
    friend class detail::Renderable_State_Observer;
};

namespace detail {

class Renderable_State_Observer {
public:
    static constexpr bool enabled = true;

    explicit Renderable_State_Observer(Renderable* renderable) noexcept : renderable_(renderable) {}

    template <class Observation>
    void observe(const Observation& observation) noexcept {
        const bool cache_updated =
            observation.event == decltype(observation.event)::cache_updated;
        renderable_->observe_state(
            cache_updated ? Renderable_Observer_Event::Cache_Updated
                          : Renderable_Observer_Event::Published,
            observation.time_ns,
            observation.cache_update_count,
            observation.publish_count
        );
    }

private:
    Renderable* renderable_;
};

} // namespace detail

namespace detail {

template <class Value>
inline constexpr bool Paint_Only_Property_Value =
    std::same_as<std::remove_cvref_t<Value>, Color> ||
    std::same_as<std::remove_cvref_t<Value>, Pen> ||
    std::same_as<std::remove_cvref_t<Value>, Brush>;

} // namespace detail

} // namespace renderive
