#pragma once
#include <cstddef>
#include <cstdint>
#include <memory>
#include <mutex>
#include <string>
#include <vector>

using Render_Node_Id = std::uint64_t;
using Render_Plan_Version = std::uint64_t;

enum class Render_Node_Kind : std::uint8_t {
    prepare,
    paint,
    composite
};

struct Render_Node {
    Render_Node_Id node_id{};
    std::uint64_t owner_id{};
    std::string name;
    Render_Node_Kind kind{};
    std::size_t execution_index{};
};

struct Render_Edge {
    Render_Node_Id from{};
    Render_Node_Id to{};
    friend bool operator==(const Render_Edge&, const Render_Edge&) = default;
};

struct Render_Graph {
    std::vector<Render_Node> nodes;
    std::vector<Render_Edge> edges;
};

class Render_Graph_Builder {
public:
    struct Task {
        const Render_Graph_Builder* builder{};
        std::uint64_t generation{};
        std::size_t index{};
        friend bool operator==(Task, Task) = default;
    };
    Task emplace(Render_Node_Id node_id, std::uint64_t owner_id, std::string name,
                 Render_Node_Kind kind);
    void precede(Task from, Task to);
    [[nodiscard]] const Render_Graph& graph() const noexcept;
    [[nodiscard]] Render_Graph finish() &&;
private:
    void validate(Task task) const;
    bool reaches(std::size_t from, std::size_t target) const;
    Render_Graph graph_;
    std::uint64_t generation_{1};
};

struct Render_Plan {
    Render_Plan_Version version{};
    Render_Graph graph;
};

class Render_Plan_History {
public:
    std::shared_ptr<const Render_Plan> publish(Render_Graph graph);
    [[nodiscard]] std::shared_ptr<const Render_Plan> current() const;
    [[nodiscard]] std::shared_ptr<const Render_Plan> find(Render_Plan_Version version) const;
private:
    mutable std::mutex mutex_;
    std::vector<std::shared_ptr<const Render_Plan>> plans_;
    Render_Plan_Version next_version_{1};
};

struct Render_Plan_Difference {
    std::vector<Render_Node_Id> added_nodes;
    std::vector<Render_Node_Id> removed_nodes;
    std::vector<Render_Edge> added_edges;
    std::vector<Render_Edge> removed_edges;
};

[[nodiscard]] Render_Plan_Difference compare_render_plans(const Render_Plan& before,
                                                          const Render_Plan& after);
