#pragma once

#include <atomic>
#include <condition_variable>
#include <concepts>
#include <cstdint>
#include <deque>
#include <exception>
#include <functional>
#include <memory>
#include <memory_resource>
#include <mutex>
#include <optional>
#include <thread>
#include <unordered_map>
#include <utility>
#include <variant>
#include <vector>

#include "renderive/base/memory/Memory_Resource.hpp"
#include "renderive/capture/Capture.hpp"
#include "renderive/frame_control/base/Frame_Control_Strategy_Base.hpp"
#include "renderive/renderable/Renderable_Graph_Builder.hpp"
#include "renderive/renderable/base/Renderable_Base.hpp"
#include "renderive/renderable/base/Renderive_Owner.hpp"
#include "renderive/render_graph/Render_Plan.hpp"
#include "Abstract_Frame.hpp"
#include "Frame_Render_Snapshot.hpp"
#include "Frame_Viewport.hpp"
#include "Render_Contexts.hpp"
#include "Scene_Lifetime.hpp"

class Color_Cache;

class Scene_Compositor {
public:
    virtual ~Scene_Compositor() = default;
    virtual void begin_composite(const Composite_Render_Context& context) = 0;
    virtual void composite(const Composite_Render_Context& context) = 0;
};

class Scene_Base {
private:
    struct Render_Completion;

public:
    using Renderable = renderive_Owner<Renderable_Base>;
    using Const_Renderable = renderive_Owner<const Renderable_Base>;

    class Attach_Builder;

    class Renderable_Editor {
    public:
        void attach(Renderable renderable);
        void detach(const Renderable& renderable);
        void set_display_parent(const Renderable& child, const Renderable& parent);
        void add_display_parent(const Renderable& child, const Renderable& parent);
        void clear_display_parent(const Renderable& child);
        void set_dependency_parent(const Renderable& child, const Renderable& parent);
        void add_dependency_parent(const Renderable& child, const Renderable& parent);
        void clear_dependency_parent(const Renderable& child);
    private:
        explicit Renderable_Editor(Scene_Base& scene) : scene_(scene) {}
        Scene_Base& scene_;
        friend class Scene_Base;
        friend class Attach_Builder;
    };

    class Attach_Builder {
    public:
        Attach_Builder(const Attach_Builder&) = delete;
        Attach_Builder& operator=(const Attach_Builder&) = delete;
        Attach_Builder(Attach_Builder&&) = delete;
        Attach_Builder& operator=(Attach_Builder&&) = delete;
        ~Attach_Builder();
        void attach(Renderable renderable);
        void set_display_parent(const Renderable& child, const Renderable& parent);
        void add_display_parent(const Renderable& child, const Renderable& parent);
        void clear_display_parent(const Renderable& child);
        void set_dependency_parent(const Renderable& child, const Renderable& parent);
        void add_dependency_parent(const Renderable& child, const Renderable& parent);
        void clear_dependency_parent(const Renderable& child);
    private:
        explicit Attach_Builder(Scene_Base& scene);
        Scene_Base& scene_;
        std::unique_lock<std::recursive_mutex> task_lock_;
        Renderable_Editor editor_;
        friend class Scene_Base;
    };

    using Renderable_Edit = std::function<void(Renderable_Editor&)>;

    enum class Observation_Event {
        render_submitted,
        render_started,
        render_completed,
        render_failed
    };

    struct Observation {
        Observation_Event event{};
        std::uint64_t time_ns{};
        std::uint64_t render_sequence{};
        std::uint64_t scene_state_revision{};
        std::size_t renderable_count{};
    };

    struct Topology_Relationship {
        Const_Renderable child;
        Const_Renderable parent;
    };

    struct Topology_Snapshot {
        std::vector<Const_Renderable> renderables;
        std::vector<Topology_Relationship> display;
        std::vector<Topology_Relationship> dependency;
    };

    Scene_Base();
    explicit Scene_Base(std::pmr::memory_resource& upstream_memory_resource);
    Scene_Base(const Scene_Base&) = delete;
    Scene_Base& operator=(const Scene_Base&) = delete;
    Scene_Base(Scene_Base&&) = delete;
    Scene_Base& operator=(Scene_Base&&) = delete;
    virtual ~Scene_Base();

    void render();
    void render(Abstract_Frame& frame);
    void wait_for_render();
    void publish_frame_state();
    void notify_model_dirty() noexcept;

    Attach_Builder attach_builder();
    void edit_renderables(Renderable_Edit edit);

    [[nodiscard]] std::size_t renderable_count() const;
    [[nodiscard]] Topology_Snapshot topology_snapshot() const;
    [[nodiscard]] std::vector<Const_Renderable> paint_order_snapshot() const;
    [[nodiscard]] std::shared_ptr<const Render_Plan> render_plan_snapshot() const;
    [[nodiscard]] std::shared_ptr<const Render_Plan> find_render_plan(
        Render_Plan_Version version) const;

    Capture_Session_Id capture_next_frame();
    Capture_Session_Id capture_frames(std::size_t count);
    [[nodiscard]] Capture_Controller_State capture_state() const noexcept;
    [[nodiscard]] std::optional<Capture_Session> capture_session(
        Capture_Session_Id session_id) const;
    [[nodiscard]] std::vector<Capture_Session_Id> capture_sessions() const;
    [[nodiscard]] std::vector<Node_Statistics> node_statistics(
        Capture_Session_Id session_id) const;
    [[nodiscard]] std::vector<Plan_Version_Statistics> plan_version_statistics(
        Capture_Session_Id session_id) const;

    [[nodiscard]] static std::size_t task_executor_worker_count() noexcept;
    [[nodiscard]] std::pmr::memory_resource& memory_resource() const noexcept;
    [[nodiscard]] std::pmr::memory_resource& upstream_memory_resource() const noexcept;
    Frame_Control_Strategy_Base& frame_control_strategy();
    const Frame_Control_Strategy_Base& frame_control_strategy() const;


protected:
    virtual Frame_Control_Strategy_Base& frame_control_strategy_impl();
    virtual const Frame_Control_Strategy_Base& frame_control_strategy_impl() const;
    virtual std::shared_ptr<Color_Cache> make_renderable_color_cache();
    virtual Frame_Viewport frame_viewport() const;
    virtual std::uint64_t acquire_scene_state();
    virtual void observe_scene(const Observation& observation) noexcept;
    virtual std::uint64_t observer_now_ns() const noexcept;

    std::unique_lock<std::recursive_mutex> lock_render_idle();
    [[nodiscard]] bool is_render_worker_thread() const noexcept;
    bool consume_model_dirty() noexcept;
    void invalidate_renderables();
    void shutdown() noexcept;

private:
    friend class Renderable_Base;

    struct Topology_Node {
        explicit Topology_Node(std::pmr::memory_resource& memory_resource)
            : parents(&memory_resource), children(&memory_resource) {}

        std::pmr::vector<Renderable_Id> parents;
        std::pmr::vector<Renderable_Id> children;
    };

    class Id_Topology {
    public:
        explicit Id_Topology(std::pmr::memory_resource& memory_resource)
            : nodes_(&memory_resource) {}

        void attach(Renderable_Id id);
        void erase(Renderable_Id id);
        bool replace_parents(Renderable_Id child, const std::vector<Renderable_Id>& parents);
        bool add_parent(Renderable_Id child, Renderable_Id parent);
        bool clear_parents(Renderable_Id child);
        [[nodiscard]] bool contains(Renderable_Id id) const noexcept;
        [[nodiscard]] bool isolated(Renderable_Id id) const;
        [[nodiscard]] const Topology_Node& node(Renderable_Id id) const;
        [[nodiscard]] std::vector<Renderable_Id> ids() const;
        [[nodiscard]] std::vector<Renderable_Id> order() const;

    private:
        void validate_endpoint(Renderable_Id id) const;
        static void erase_id(std::pmr::vector<Renderable_Id>& values, Renderable_Id id) noexcept;

        std::pmr::unordered_map<Renderable_Id, Topology_Node> nodes_;
    };

    using Composite_Render_Node_Function =
        std::function<void(const Composite_Render_Context&)>;
    using Execution_Function =
        std::variant<Prepare_Render_Node_Function, Paint_Render_Node_Function,
                     Composite_Render_Node_Function>;

    struct Execution_Binding {
        Renderable owner;
        std::optional<std::size_t> renderable_index;
        Execution_Function function;
    };

    struct Render_Task {
        using Frame = std::variant<std::shared_ptr<Abstract_Frame>,
                                   std::reference_wrapper<Abstract_Frame>>;

        explicit Render_Task(std::pmr::memory_resource& memory_resource)
            : execution_bindings(&memory_resource) {}

        std::shared_ptr<const Frame_Render_Snapshot> snapshot;
        std::shared_ptr<const Render_Plan> plan;
        std::pmr::vector<Execution_Binding> execution_bindings;
        Frame frame;
        std::shared_ptr<Render_Completion> completion;
    };

    struct Renderable_Edit_Command {
        Renderable_Edit edit;
    };

    class Execution_Context;
    class Render_Execution_Scope;

    struct Render_Completion {
        std::exception_ptr exception;
        bool completed{};
        bool observed{};
    };

    void submit_render(Abstract_Frame* frame);
    void enqueue_renderable_edit(Renderable_Edit edit);
    void enqueue_renderable_edit_locked(Renderable_Edit edit);
    void request_render_graph_rebuild(Renderable_Base& renderable);
    [[nodiscard]] std::shared_ptr<Frame_Render_Snapshot> snapshot_live_model();
    std::shared_ptr<const Render_Plan> compile_render_plan(
        const Frame_Render_Snapshot& snapshot, Render_Task& task);
    void render_loop();
    void execute_taskflow(Render_Task& task);
    void validate_renderable_scene(const Renderable_Base& renderable) const;
    [[nodiscard]] bool is_renderable_attached_locked(
        const Renderable& renderable) const;
    void validate_structure_locked();
    void cleanup_detached_topology_locked();
    [[noreturn]] static void structure_fail_fast(const char* stage, std::exception_ptr exception = {}) noexcept;

    inline static thread_local Scene_Base* active_execution_scene_{};
    inline static thread_local Scene_Base* active_renderable_edit_scene_{};
    inline static thread_local Scene_Base* active_submitted_observer_scene_{};

    std::shared_ptr<Scene_Lifetime> scene_lifetime_;
    std::shared_ptr<Scene_Memory_Domain> memory_domain_;
    std::atomic_bool model_dirty_{true};
    std::pmr::unordered_map<Renderable_Id, Renderable> renderables_;
    Id_Topology display_topology_;
    Id_Topology dependency_topology_;
    std::pmr::unordered_map<Renderable_Id, std::shared_ptr<Color_Cache>> color_caches_;
    mutable std::mutex model_mutex_;

    mutable std::recursive_mutex task_mutex_;
    std::condition_variable_any task_ready_;
    std::condition_variable_any render_completed_;
    std::thread worker_;
    Render_Task task_;
    std::pmr::deque<Renderable_Edit_Command> renderable_edit_queue_;
    std::shared_ptr<Render_Completion> current_completion_;
    std::exception_ptr pending_exception_;
    bool pending_exception_observed_{};
    std::size_t deferred_render_count_{};
    std::uint64_t render_sequence_{};
    bool task_pending_{};
    bool rendering_{};
    bool editing_renderables_{};
    bool runtime_started_{};
    bool stop_{};

    const Render_Node_Id composite_begin_node_id_;
    Render_Plan_History render_plan_history_;
    Capture_Controller capture_controller_;
    Capture_Repository capture_repository_;
};

class Scene_2D_Base : public Scene_Base {
public:
    Scene_2D_Base() = default;
    explicit Scene_2D_Base(std::pmr::memory_resource& memory_resource)
        : Scene_Base(memory_resource) {}
};

class Scene_3D_Base : public Scene_Base {
public:
    Scene_3D_Base() = default;
    explicit Scene_3D_Base(std::pmr::memory_resource& memory_resource)
        : Scene_Base(memory_resource) {}
};
