#pragma once

#include "Gallery_Capture_Json.h"
#include "Gallery_Observer_Adminive.h"
#include "Gallery_Renderables.h"

#include <renderive/scene/base/Scene_Base.hpp>

#include <nlohmann/json.hpp>

#include <algorithm>
#include <array>
#include <charconv>
#include <concepts>
#include <functional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>

namespace renderive::web {

class Gallery_Controls final {
public:
    using Json = nlohmann::json;

    template <class Object>
    void add(Object& object) {
        const auto descriptor = adminive::Type_Descriptor<Object>::get();
        add(descriptor.name(), descriptor.label(), object);
    }

    template <class Object>
    void add(std::string id, Object& object) {
        add(std::move(id), adminive::Type_Descriptor<Object>::get().label(), object);
    }

    [[nodiscard]] Json resources() const {
        Json result = Json::array();
        for (const auto& entry : entries_) {
            Json resource = entry.resource();
            resource["target"] = entry.id;
            resource["view"]["title"] = entry.title;
            result.push_back(std::move(resource));
        }
        return result;
    }

    [[nodiscard]] Json observers(const Scene_Base& scene) const {
        Json result = Json::array();
        const auto topology = scene.topology_snapshot();
        for (std::size_t index = 0; index < topology.renderables.size(); ++index) {
            const auto* renderable =
                dynamic_cast<const renderive::Renderable*>(topology.renderables[index].get());
            if (!renderable)
                continue;
            const Entry* entry = find(renderable);
            std::string title = entry ? entry->title : renderable->object_name();
            if (title.empty())
                title = "渲染节点 " + std::to_string(index + 1);
            Json resource{
                {"descriptor", adminive::to_descriptor_json<
                    Json, renderive::Renderable_Observation>()},
                {"data", adminive::to_frontend_json<Json>(renderable->observation())},
                {"target", entry ? entry->id : node_id(index)},
                {"title", std::move(title)}
            };
            result.push_back(std::move(resource));
        }
        return result;
    }

    [[nodiscard]] Json render_plan(const Scene_Base& scene) const {
        return gallery_render_plan_json(scene);
    }
    [[nodiscard]] adminive::Update_Result apply(std::string_view target,
                                                 const Json& patch) const {
        for (const auto& entry : entries_) {
            if (entry.id == target)
                return entry.apply(patch);
        }
        adminive::Update_Result result;
        result.success = false;
        result.message = "unknown gallery control target: " + std::string(target);
        return result;
    }

private:
    struct Entry {
        std::string id;
        std::string title;
        std::function<Json()> resource;
        std::function<adminive::Update_Result(const Json&)> apply;
        renderive::Renderable* renderable{};
    };

    template <class Object>
    void add(std::string id, std::string title, Object& object) {
        using Model = adminive::Object_Model_Type<Object>;
        auto* target = &object;
        renderive::Renderable* renderable{};
        if constexpr (std::derived_from<Object, renderive::Renderable>)
            renderable = target;
        entries_.push_back({
            std::move(id), std::move(title),
            [target] {
                return Json{
                    {"descriptor", adminive::to_descriptor_json<Json, Object>()},
                    {"view", adminive::to_view_json<Json, Model>(
                        adminive::describe_edit_view<Model>())},
                    {"data", adminive::to_frontend_json<Json>(*target)}
                };
            },
            [target](const Json& patch) {
                return adminive::apply_frontend_patch<Json>(*target, patch);
            },
            renderable
        });
    }

    [[nodiscard]] const Entry* find(const Renderable_Base* renderable) const noexcept {
        const auto found = std::find_if(entries_.begin(), entries_.end(),
            [renderable](const Entry& entry) { return entry.renderable == renderable; });
        return found == entries_.end() ? nullptr : &*found;
    }

    static std::string node_id(std::size_t index) {
        return "renderable-" + std::to_string(index);
    }

    std::vector<Entry> entries_;
};

} // namespace renderive::web
