#include "../app/Gallery_Plot_Session.h"
#include "../app/Gallery_Observer_Adminive.h"
#include "../app/Gallery_Protocol.h"
#include "../app/Gallery_Renderables.h"
#include "../app/Pixel_Frame.h"
#include "../app/Web_Event_Adapter.h"
#include "../app/Web_Plot_Session.h"

#include <gtest/gtest.h>
#include <nlohmann/json.hpp>

#include <algorithm>
#include <array>
#include <chrono>
#include <cmath>
#include <cstring>
#include <memory>
#include <set>
#include <string>
#include <string_view>
#include <thread>
#include <tuple>
#include <variant>
#include <vector>

namespace renderive::web {
namespace {

struct Spatial_Test_Event {
    int layer{};
};

struct Spatial_Test_Decoder {
    using event_type = Spatial_Test_Event;

    static std::optional<event_type> decode(const Json::Value& root, std::string_view) {
        if (root["type"].asString() != "spatial_test" || !root["layer"].isInt())
            return std::nullopt;
        return Spatial_Test_Event{root["layer"].asInt()};
    }
};

using Extended_Web_Event = Basic_Web_Event<Event, Pointer_Event, Wheel_Event,
                                            Key_Event, Spatial_Test_Event>;
using Extended_Web_Event_Adapter = Basic_Web_Event_Adapter<
    Extended_Web_Event, Web_Transport_Event_Decoder, Render_2D_Web_Event_Decoder,
    Web_Demo_Control_Decoder, Spatial_Test_Decoder>;

std::uint32_t read_u32_le(const std::string& value, std::size_t offset) {
    return static_cast<std::uint8_t>(value[offset]) |
           (static_cast<std::uint32_t>(static_cast<std::uint8_t>(value[offset + 1])) << 8U) |
           (static_cast<std::uint32_t>(static_cast<std::uint8_t>(value[offset + 2])) << 16U) |
           (static_cast<std::uint32_t>(static_cast<std::uint8_t>(value[offset + 3])) << 24U);
}

nlohmann::json parse_json(const std::string& value) {
    return nlohmann::json::parse(value);
}

Gallery_Request gallery_request(Gallery_Request_Kind kind, std::string message) {
    return {kind, std::move(message)};
}

std::string open_message(std::string_view case_id, std::string_view frame_mode) {
    return std::string(R"({"category":"event","type":"gallery_open","case":")") +
           std::string(case_id) + R"(","frame_mode":")" + std::string(frame_mode) + R"("})";
}

nlohmann::json response_json(std::optional<Web_Response> response) {
    if (!response) {
        ADD_FAILURE() << "expected a Web response";
        return nlohmann::json::object();
    }
    EXPECT_EQ(response->type, Web_Response_Type::Json);
    return parse_json(response->payload);
}

nlohmann::json observe_telemetry(Gallery_Plot_Session& session) {
    return response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Observe,
        R"({"category":"event","type":"gallery_observe"})"))).value(
            "telemetry", nlohmann::json::object());
}

nlohmann::json invoke_action(Gallery_Plot_Session& session, std::string_view action_id,
                             std::optional<double> argument = std::nullopt) {
    nlohmann::json request{{"category", "event"}, {"type", "gallery_action"},
                           {"action", action_id}};
    if (argument)
        request["argument"] = *argument;
    return response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Action, request.dump())));
}

nlohmann::json patch_controls(Gallery_Plot_Session& session, nlohmann::json patch,
                              std::string_view target = "scene") {
    const nlohmann::json request{{"category", "event"}, {"type", "gallery_patch"},
                                 {"target", target}, {"patch", std::move(patch)}};
    return response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Patch, request.dump())));
}

template <class Predicate>
bool wait_for_condition(Predicate&& predicate,
                        std::chrono::milliseconds timeout = std::chrono::milliseconds(750)) {
    const auto deadline = std::chrono::steady_clock::now() + timeout;
    do {
        if (predicate())
            return true;
        std::this_thread::sleep_for(std::chrono::milliseconds(5));
    } while (std::chrono::steady_clock::now() < deadline);
    return predicate();
}

std::optional<Web_Response> wait_for_pixel_frame(Gallery_Plot_Session& session,
                                                  std::uint32_t width,
                                                  std::uint32_t height) {
    std::optional<Web_Response> frame;
    wait_for_condition([&] {
        frame = session.handle(Frame_Request{});
        return frame && frame->type == Web_Response_Type::Pixels &&
               frame->payload.size() >= pixel_frame_header_size &&
               read_u32_le(frame->payload, 4) == width &&
               read_u32_le(frame->payload, 8) == height;
    });
    return frame;
}

void set_gallery_view_active(Gallery_Plot_Session& session, bool active) {
    const auto event = Web_Event_Adapter::decode(active ?
        R"({"category":"event","type":"show"})" :
        R"({"category":"event","type":"hide"})");
    ASSERT_TRUE(event.has_value());
    EXPECT_FALSE(session.handle(*event).has_value());
}

std::uint64_t successful_render_count(Gallery_Plot_Session& session) {
    return observe_telemetry(session).at("performance").at("successful_render_count")
        .get<std::uint64_t>();
}

TEST(RenderiveWebBridge, DecodesOnlyEventMessages) {
    const auto resize = Web_Event_Adapter::decode(
        R"({"category":"event","type":"resize","width":800,"height":450})");
    ASSERT_TRUE(resize.has_value());
    ASSERT_TRUE(std::holds_alternative<Viewport_Resize>(*resize));
    EXPECT_EQ(std::get<Viewport_Resize>(*resize).size, (Size{800, 450}));

    const auto pointer = Web_Event_Adapter::decode(
        R"({"category":"event","type":"pointer_press","x":22.5,"y":18,"button":"left","buttons":1,"modifiers":3})");
    ASSERT_TRUE(pointer.has_value());
    ASSERT_TRUE(std::holds_alternative<Pointer_Event>(*pointer));
    EXPECT_EQ(std::get<Pointer_Event>(*pointer).button, Mouse_Button::Left);
    EXPECT_DOUBLE_EQ(std::get<Pointer_Event>(*pointer).position.x, 22.5);

    EXPECT_FALSE(Web_Event_Adapter::decode(R"({"category":"command","type":"frame"})"));
    EXPECT_FALSE(Web_Event_Adapter::decode("not-json"));

    const auto catalog = Web_Event_Adapter::decode(
        R"({"category":"event","type":"gallery_catalog"})");
    ASSERT_TRUE(catalog.has_value());
    ASSERT_TRUE(std::holds_alternative<Gallery_Request>(*catalog));
    EXPECT_EQ(std::get<Gallery_Request>(*catalog).kind, Gallery_Request_Kind::Catalog);

    const auto open = Web_Event_Adapter::decode(
        R"({"category":"event","type":"gallery_open","case":"spectrum","frame_mode":"manual"})");
    ASSERT_TRUE(open.has_value());
    EXPECT_EQ(std::get<Gallery_Request>(*open).kind, Gallery_Request_Kind::Open);

    const auto observe = Web_Event_Adapter::decode(
        R"({"category":"event","type":"gallery_observe"})");
    ASSERT_TRUE(observe.has_value());
    EXPECT_EQ(std::get<Gallery_Request>(*observe).kind, Gallery_Request_Kind::Observe);

    const auto refresh = Web_Event_Adapter::decode(
        R"({"category":"event","type":"gallery_refresh"})");
    ASSERT_TRUE(refresh.has_value());
    EXPECT_EQ(std::get<Gallery_Request>(*refresh).kind, Gallery_Request_Kind::Refresh);

    const auto reset_monitoring = Web_Event_Adapter::decode(
        R"({"category":"event","type":"gallery_reset_monitoring"})");
    ASSERT_TRUE(reset_monitoring.has_value());
    EXPECT_EQ(std::get<Gallery_Request>(*reset_monitoring).kind,
              Gallery_Request_Kind::Reset_Monitoring);

    const auto bounded = Web_Event_Adapter::decode(
        R"({"category":"event","type":"resize","width":1e100,"height":-1e100})");
    ASSERT_TRUE(bounded.has_value());
    EXPECT_EQ(std::get<Viewport_Resize>(*bounded).size, (Size{1920, 180}));
}

TEST(RenderiveWebBridge, EncodesRgbaPixelFrame) {
    const Pixel pixels[] = {pack_rgba(255, 0, 0), pack_rgba(0, 255, 0)};
    const Image_View image{reinterpret_cast<const std::byte*>(pixels), 2, 1,
                           static_cast<int>(sizeof(pixels)), Pixel_Format::Premultiplied_32};
    const std::string frame = encode_pixel_frame(image);
    ASSERT_EQ(frame.size(), pixel_frame_header_size + 8);
    EXPECT_EQ(frame.substr(0, 4), "RVP1");
    EXPECT_EQ(read_u32_le(frame, 4), 2U);
    EXPECT_EQ(read_u32_le(frame, 8), 1U);
    EXPECT_EQ(read_u32_le(frame, 12), 8U);
    const auto* rgba = reinterpret_cast<const std::uint8_t*>(frame.data() + pixel_frame_header_size);
    EXPECT_EQ(rgba[0], 255);
    EXPECT_EQ(rgba[1], 0);
    EXPECT_EQ(rgba[2], 0);
    EXPECT_EQ(rgba[3], 255);
    EXPECT_EQ(rgba[4], 0);
    EXPECT_EQ(rgba[5], 255);
}

TEST(RenderiveWebBridge, LegacyReceiverStillRendersPixels) {
    Web_Plot_Session session;
    EXPECT_FALSE(session.handle(Viewport_Resize{{640, 360}}).has_value());
    EXPECT_FALSE(session.handle(Set_Demo_Mode{Demo_Mode::Usb}).has_value());
    const auto frame = session.handle(Frame_Request{});
    ASSERT_TRUE(frame.has_value());
    EXPECT_EQ(frame->type, Web_Response_Type::Pixels);
    ASSERT_GE(frame->payload.size(), pixel_frame_header_size);
    EXPECT_EQ(frame->payload.substr(0, 4), "RVP1");
    EXPECT_EQ(read_u32_le(frame->payload, 4), 640U);
    EXPECT_EQ(read_u32_le(frame->payload, 8), 360U);
}

TEST(RenderiveWebGallery, AdminiveCatalogCoversEveryControlCaseAndThreeModes) {
    const nlohmann::json catalog = parse_json(Gallery_Protocol::catalog_json());
    EXPECT_EQ(catalog.at("category"), "gallery");
    EXPECT_EQ(catalog.at("type"), "catalog");
    EXPECT_EQ(catalog.at("protocol"), "renderive.control-gallery");
    EXPECT_EQ(catalog.at("protocol_version"), 4);
    EXPECT_EQ(catalog.at("case_descriptor").at("protocol"), "adminive.resource");
    EXPECT_EQ(catalog.at("cases").size(), 8U);
    EXPECT_EQ(catalog.at("frame_modes").size(), 3U);
    EXPECT_EQ(catalog.at("coverage").at("page_count"), 3);
    EXPECT_EQ(catalog.at("coverage").at("canvas_count"), 24);
    EXPECT_GT(catalog.at("coverage").at("manual_control_count").get<std::size_t>(), 180U);
    EXPECT_GT(catalog.at("coverage").at("manual_action_count").get<std::size_t>(), 50U);
    EXPECT_EQ(catalog.at("coverage").at("frequency_modes"),
              nlohmann::json::array({"spectrum", "afterglow", "sweep_spectrum"}));
    EXPECT_EQ(catalog.at("coverage").at("image_interpolation_modes"),
               nlohmann::json::array({"Nearest", "Bilinear", "Bicubic"}));
}

TEST(RenderiveWebGallery, CatalogOwnsDashboardFieldsAndDynamicBehavior) {
    const nlohmann::json catalog = parse_json(Gallery_Protocol::catalog_json());
    const auto& dashboard = catalog.at("dashboard");
    EXPECT_EQ(dashboard.at("performance").at("fields").size(), 26U);
    EXPECT_EQ(dashboard.at("limits").at("fields").size(), 4U);
    EXPECT_EQ(dashboard.at("observer").at("sections").size(), 4U);
    std::set<std::string> client_sources;
    for (const auto& section : dashboard.at("observer").at("sections")) {
        for (const auto& field : section.at("fields")) {
            const std::string source = field.at("source").get<std::string>();
            if (source.starts_with("client_performance."))
                client_sources.insert(source);
        }
    }
    constexpr std::array<std::string_view, 20> client_fields{
        "display_interval_latest_ms", "display_interval_ms", "display_interval_average_ms",
        "display_interval_p95_ms", "display_interval_p99_ms", "display_interval_deviation_ms",
        "frame_round_trip_ms", "frame_round_trip_average_ms", "frame_round_trip_p95_ms",
        "frame_round_trip_p99_ms", "frame_round_trip_deviation_ms",
        "transport_fps", "presentation_fps",
        "websocket_buffered_bytes", "overwritten_pixel_frames", "changed_pixel_frames",
        "duplicate_pixel_frames", "frame_request_timeout_count", "last_pixel_receive_age_ms",
        "last_pixel_change_age_ms"
    };
    EXPECT_EQ(client_sources.size(), client_fields.size());
    for (const auto field : client_fields)
        EXPECT_TRUE(client_sources.contains("client_performance." + std::string(field))) << field;
    for (const auto& mode : catalog.at("frame_modes")) {
        EXPECT_TRUE(mode.contains("frame_button_label"));
        EXPECT_TRUE(mode.contains("request_after_response"));
        EXPECT_TRUE(mode.contains("request_on_animation_frame"));
        EXPECT_TRUE(mode.contains("observer_visible"));
        EXPECT_TRUE(mode.contains("accent"));
    }
    Gallery_Plot_Session session;
    const auto contract = response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Open, open_message("spectrum", "manual"))));
    const auto action = std::find_if(contract.at("actions").at("data").begin(),
                                     contract.at("actions").at("data").end(),
                                     [](const auto& item) { return item.at("id") == "mode_render"; });
    ASSERT_NE(action, contract.at("actions").at("data").end());
    EXPECT_TRUE(action->at("request_frame"));
}

TEST(RenderiveWebGallery, ObserverTelemetryAndDashboardUseAdminiveDescriptors) {
    const auto catalog = parse_json(Gallery_Protocol::catalog_json());
    const auto& observer_contract = catalog.at("dashboard").at("observer");
    const auto& observer_descriptor = observer_contract.at("descriptor");
    const auto& feedback_descriptor =
        observer_contract.at("consumer_feedback_descriptor");
    EXPECT_EQ(observer_descriptor.at("label"), "内核观察器");
    EXPECT_EQ(feedback_descriptor.at("label"), "消费者反馈");

    Gallery_Plot_Session session;
    const auto opened = response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Open, open_message("spectrum", "manual"))));
    const auto& telemetry = opened.at("telemetry");
    const auto& observer = telemetry.at("kernel_observer");
    const auto& feedback = telemetry.at("consumer_feedback");

    EXPECT_EQ(observer.size(), observer_descriptor.at("fields").size());
    for (const auto& field : observer_descriptor.at("fields"))
        EXPECT_TRUE(observer.contains(field.at("name").get<std::string>()));
    EXPECT_EQ(feedback.size(), feedback_descriptor.at("fields").size());
    for (const auto& field : feedback_descriptor.at("fields"))
        EXPECT_TRUE(feedback.contains(field.at("name").get<std::string>()));
    EXPECT_EQ(observer.at("mode"), "manual");
    EXPECT_FALSE(observer.contains("task_worker_count"));
    ASSERT_TRUE(telemetry.contains("performance_capture"));
    EXPECT_TRUE(telemetry.at("performance_capture").contains("controller"));
    EXPECT_TRUE(telemetry.at("performance_capture").contains("sessions"));
    EXPECT_TRUE(telemetry.at("performance_capture").contains("plans"));
    EXPECT_FALSE(telemetry.contains("low_latency_limit"));
}

TEST(RenderiveWebGallery, ActionsComeFromStructiveTypeMetadata) {
    using Spectrum_Schema = structive::type_descriptor_schema_t<Gallery_Spectrum>;
    using Time_Axis_Schema = structive::type_descriptor_schema_t<Gallery_Time_Axis>;
    using Manual_Strategy =
        Manual_Refresh_Strategy<Scene2D_Frame_Data, std::mutex, Observer_State<>>;
    using Manual_Schema = structive::type_descriptor_schema_t<Manual_Strategy>;
    static_assert(Spectrum_Schema::template type_attribute_count<
                      Gallery_Action_Category> == 12);
    static_assert(Time_Axis_Schema::template type_attribute_count<
                      Gallery_Action_Category> == 2);
    static_assert(Manual_Schema::template type_attribute_count<
                      Gallery_Action_Category> == 5);

    std::vector<Gallery_Action_Model> actions;
    append_gallery_actions<Gallery_Spectrum>(actions);
    EXPECT_EQ(actions.size(), 12U);
    EXPECT_TRUE(std::any_of(actions.begin(), actions.end(), [](const auto& action) {
        return action.id == "power_at" && action.argument_input == "number";
    }));
}

TEST(RenderiveWebBridge, ComposesAdditionalEventDecoders) {
    const auto event = Extended_Web_Event_Adapter::decode(
        R"({"category":"event","type":"spatial_test","layer":7})");
    ASSERT_TRUE(event.has_value());
    ASSERT_TRUE(std::holds_alternative<Spatial_Test_Event>(*event));
    EXPECT_EQ(std::get<Spatial_Test_Event>(*event).layer, 7);
}

TEST(RenderiveWebGallery, AdminiveResourcesPatchTheRealRenderableState) {
    Gallery_Plot_Session session;
    auto response = response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Open, open_message("spectrum", "manual"))));
    ASSERT_EQ(response.at("type"), "case_state");
    const auto find_resource = [](const nlohmann::json& value, std::string_view target)
        -> const nlohmann::json& {
        const auto& resources = value.at("controls").at("resources");
        const auto found = std::find_if(resources.begin(), resources.end(),
            [target](const auto& resource) {
                return resource.at("target").template get<std::string>() == target;
            });
        if (found == resources.end())
            throw std::out_of_range("missing gallery resource");
        return *found;
    };

    const auto& spectrum = find_resource(response, "spectrum");
    EXPECT_EQ(spectrum.at("descriptor").at("name"), "spectrum");
    EXPECT_TRUE(spectrum.at("data").at("frequency_range").is_object());
    EXPECT_EQ(spectrum.at("data").at("frequency_point_size"), 512);
    const auto partition_field = std::find_if(
        spectrum.at("descriptor").at("fields").begin(),
        spectrum.at("descriptor").at("fields").end(),
        [](const auto& field) { return field.at("name") == "partition_count"; });
    ASSERT_NE(partition_field, spectrum.at("descriptor").at("fields").end());
    EXPECT_EQ(partition_field->at("presentation").at("label"), "固定分区数");
    EXPECT_EQ(partition_field->at("presentation").at("description"),
              "固定模式下的并行任务数；1 表示不分区");

    const auto partition_mode_field = std::find_if(
        spectrum.at("descriptor").at("fields").begin(),
        spectrum.at("descriptor").at("fields").end(),
        [](const auto& field) { return field.at("name") == "partition_mode"; });
    ASSERT_NE(partition_mode_field, spectrum.at("descriptor").at("fields").end());
    EXPECT_EQ(partition_mode_field->at("presentation").at("label"), "任务分区模式");
    EXPECT_EQ(spectrum.at("descriptor").at("label"), "实时频谱");

    response = response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Patch,
        R"({"category":"event","type":"gallery_patch","target":"spectrum","patch":{"frequency_point_size":1024,"partition_mode":"Fixed","partition_count":4,"frequency_range":{"origin":90000000},"current_pen":{"color":"#ff8844"},"interpolation_mode":"Cubic_Value"}})")));
    ASSERT_EQ(response.at("type"), "case_state");
    const auto& updated = find_resource(response, "spectrum").at("data");
    EXPECT_EQ(updated.at("frequency_point_size"), 1024);
    EXPECT_EQ(updated.at("partition_count"), 4);
    EXPECT_EQ(updated.at("partition_mode"), "Fixed");
    EXPECT_EQ(updated.at("frequency_range").at("origin"), 90'000'000);
    EXPECT_EQ(updated.at("current_pen").at("color"), "#ff8844");
    EXPECT_EQ(updated.at("interpolation_mode"), "Cubic_Value");

    const auto invalid = response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Patch,
        R"({"category":"event","type":"gallery_patch","target":"spectrum","patch":{"current_pen":{"color":"not-a-color"}}})")));
    EXPECT_EQ(invalid.at("type"), "error");

    const auto foreign = response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Patch,
        R"({"category":"event","type":"gallery_patch","target":"waterfall","patch":{"visible_range_only":false}})")));
    EXPECT_EQ(foreign.at("type"), "error");
}

TEST(RenderiveWebGallery, ExposesStateObserversAndOneCompleteRenderDag) {
    Gallery_Plot_Session session;
    const auto opened = response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Open, open_message("spectrum", "manual"))));
    ASSERT_EQ(opened.at("type"), "case_state");

    const auto& observers = opened.at("controls").at("observers");
    const auto& graph = opened.at("controls").at("render_plan");
    EXPECT_GE(observers.size(), 7U);
    EXPECT_GT(graph.at("nodes").size(), observers.size());
    for (const std::string_view target : {"frequency_axis", "value_axis", "spectrum"}) {
        const auto found = std::find_if(observers.begin(), observers.end(),
            [target](const auto& resource) {
                return resource.at("target").template get<std::string>() == target;
            });
        ASSERT_NE(found, observers.end()) << target;
        EXPECT_EQ(found->at("descriptor").at("label"), "渲染对象观察器");
        EXPECT_EQ(found->at("descriptor").at("fields").at(0)
                      .at("presentation").at("label"),
                  "最近状态事件");
        EXPECT_EQ(found->at("data").at("event"), "Published");
        EXPECT_GT(found->at("data").at("publish_count").get<std::uint64_t>(), 0U);
        EXPECT_FALSE(found->at("data").contains("task_execution_count"));
    }

    ASSERT_TRUE(graph.at("topology_id").is_string());
    EXPECT_FALSE(graph.at("topology_id").get<std::string>().empty());
    EXPECT_FALSE(graph.at("nodes").empty());
    EXPECT_FALSE(graph.at("edges").empty());
    ASSERT_EQ(graph.at("renderables").size(), observers.size());
    std::set<std::string> kinds;
    for (const auto& node : graph.at("nodes"))
        kinds.insert(node.at("kind").get<std::string>());
    EXPECT_TRUE(kinds.contains("prepare"));
    EXPECT_TRUE(kinds.contains("paint"));
    EXPECT_TRUE(kinds.contains("composite"));
    for (const auto& renderable : graph.at("renderables")) {
        EXPECT_TRUE(renderable.contains("prepare_cache"));
        EXPECT_TRUE(renderable.contains("paint_cache"));
    }

    const auto observed = response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Observe,
        R"({"category":"event","type":"gallery_observe"})")));
    ASSERT_EQ(observed.at("type"), "observer_state");
    EXPECT_EQ(observed.at("telemetry").at("renderable_observers").size(),
              observers.size());
    EXPECT_FALSE(observed.at("telemetry").contains("render_plan"));

    const auto refreshed = response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Refresh,
        R"({"category":"event","type":"gallery_refresh"})")));
    ASSERT_EQ(refreshed.at("type"), "refresh_state");
    EXPECT_EQ(refreshed.at("controls").at("observers").size(),
              observers.size());
    EXPECT_EQ(refreshed.at("notice"), "观察数据已手动刷新");

    const auto reset = response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Reset_Monitoring,
        R"({"category":"event","type":"gallery_reset_monitoring"})")));
    ASSERT_EQ(reset.at("type"), "refresh_state");
    EXPECT_EQ(reset.at("notice"), "监测滑动窗口已重置");
    EXPECT_EQ(reset.at("telemetry").at("performance").at("render_sample_count"), 0);
    EXPECT_EQ(reset.at("telemetry").at("performance").at("pixel_encode_sample_count"), 0);
    for (const auto& observer : reset.at("telemetry").at("renderable_observers"))
        EXPECT_FALSE(observer.at("data").contains("task_execution_count"));
}

TEST(RenderiveWebGallery, PartitionControlRebuildsTheUnifiedRenderDag) {
    Gallery_Plot_Session session;
    ASSERT_EQ(response_json(session.handle(gallery_request(
                  Gallery_Request_Kind::Open,
                  open_message("spectrum", "manual")))).at("type"),
              "case_state");

    const auto render = [&session] {
        EXPECT_EQ(invoke_action(session, "mode_prepare").at("type"), "case_state");
        EXPECT_EQ(invoke_action(session, "mode_refresh").at("type"), "case_state");
        return invoke_action(session, "mode_render");
    };

    const auto single_state = patch_controls(
        session, {{"partition_mode", "Fixed"}, {"partition_count", 1}}, "spectrum");
    ASSERT_EQ(single_state.at("type"), "case_state");
    const auto single = render();
    const auto& single_graph = single.at("controls").at("render_plan");
    const std::string single_topology = single_graph.at("topology_id");

    const auto partitioned_state = patch_controls(
        session, {{"partition_count", 4}}, "spectrum");
    ASSERT_EQ(partitioned_state.at("type"), "case_state");
    const auto partitioned = render();
    const auto& partitioned_graph = partitioned.at("controls").at("render_plan");
    EXPECT_NE(partitioned_graph.at("topology_id"), single_topology);
    EXPECT_EQ(partitioned_graph.at("nodes").size(),
              single_graph.at("nodes").size() + 3U);
}

TEST(RenderiveWebGallery, PerformanceCaptureReturnsPlanFrameAnalysisAndStatistics) {
    Gallery_Plot_Session session;
    ASSERT_EQ(response_json(session.handle(gallery_request(
                  Gallery_Request_Kind::Open,
                  open_message("spectrum", "manual")))).at("type"),
              "case_state");

    const auto requested = invoke_action(session, "capture_frames", 2.0);
    ASSERT_EQ(requested.at("type"), "case_state");
    const auto& initial_capture = requested.at("telemetry").at("performance_capture");
    EXPECT_TRUE(initial_capture.at("controller").at("enabled"));
    EXPECT_EQ(initial_capture.at("controller").at("remaining_frame_count"), 2);
    ASSERT_EQ(initial_capture.at("sessions").size(), 1U);
    EXPECT_EQ(initial_capture.at("sessions").at(0).at("requested_count"), 2);

    nlohmann::json rendered;
    for (int frame = 0; frame < 2; ++frame) {
        ASSERT_EQ(invoke_action(session, "mode_prepare").at("type"), "case_state");
        ASSERT_EQ(invoke_action(session, "mode_refresh").at("type"), "case_state");
        rendered = invoke_action(session, "mode_render");
        ASSERT_EQ(rendered.at("type"), "case_state");
    }

    const auto& capture = rendered.at("telemetry").at("performance_capture");
    EXPECT_FALSE(capture.at("controller").at("enabled"));
    ASSERT_EQ(capture.at("sessions").size(), 1U);
    const auto& captured = capture.at("sessions").at(0);
    EXPECT_EQ(captured.at("captured_count"), 2);
    EXPECT_FALSE(captured.at("active"));
    ASSERT_EQ(captured.at("frames").size(), 2U);
    EXPECT_TRUE(captured.at("summary").contains("render_p95_ns"));
    EXPECT_TRUE(captured.at("summary").contains("average_parallelism"));
    EXPECT_TRUE(captured.at("summary").contains("scheduler_wait_p95_ns"));
    EXPECT_FALSE(captured.at("node_statistics").empty());
    EXPECT_FALSE(captured.at("plan_statistics").empty());
    EXPECT_FALSE(capture.at("plans").empty());

    for (const auto& frame : captured.at("frames")) {
        EXPECT_TRUE(frame.contains("frame_id"));
        EXPECT_TRUE(frame.contains("render_plan_version"));
        EXPECT_TRUE(frame.contains("render_start_ns"));
        EXPECT_TRUE(frame.contains("render_end_ns"));
        ASSERT_FALSE(frame.at("node_executions").empty());
        const auto& execution = frame.at("node_executions").at(0);
        EXPECT_TRUE(execution.contains("start_time_ns"));
        EXPECT_TRUE(execution.contains("end_time_ns"));
        EXPECT_TRUE(execution.contains("worker_id"));
        EXPECT_TRUE(execution.contains("status"));
        const auto& analysis = frame.at("analysis");
        EXPECT_TRUE(analysis.contains("critical_path"));
        EXPECT_TRUE(analysis.contains("parallel_overlap_ns"));
        EXPECT_TRUE(analysis.contains("workers"));
        const auto version = frame.at("render_plan_version");
        EXPECT_TRUE(std::any_of(capture.at("plans").begin(), capture.at("plans").end(),
            [&](const auto& plan) { return plan.at("version") == version; }));
    }

    const auto& plan = capture.at("plans").back();
    EXPECT_FALSE(plan.at("nodes").empty());
    EXPECT_FALSE(plan.at("edges").empty());
    EXPECT_TRUE(plan.contains("difference"));
    EXPECT_TRUE(plan.at("difference").contains("added_nodes"));
    const auto& statistic = captured.at("node_statistics").at(0);
    EXPECT_TRUE(statistic.contains("moving_average_ns"));
    EXPECT_TRUE(statistic.contains("p50_ns"));
    EXPECT_TRUE(statistic.contains("p95_ns"));
    EXPECT_TRUE(statistic.contains("p99_ns"));
    EXPECT_TRUE(statistic.contains("critical_path_frequency"));
    EXPECT_TRUE(statistic.contains("average_scheduler_wait_ns"));
}

TEST(RenderiveWebGallery, AxisSwapAndEveryPlotDirectionAreEditableAndRenderable) {
    const auto patch = [](Gallery_Plot_Session& session, std::string_view target,
                          nlohmann::json value) {
        nlohmann::json request{{"category", "event"}, {"type", "gallery_patch"},
                               {"target", target}, {"patch", std::move(value)}};
        return response_json(session.handle(gallery_request(
            Gallery_Request_Kind::Patch, request.dump())));
    };
    const auto resource_data = [](const nlohmann::json& state, std::string_view target)
        -> const nlohmann::json& {
        const auto& resources = state.at("controls").at("resources");
        const auto found = std::find_if(resources.begin(), resources.end(),
            [target](const auto& resource) {
                return resource.at("target").template get<std::string>() == target;
            });
        if (found == resources.end())
            throw std::out_of_range("missing gallery resource");
        return found->at("data");
    };

    for (const std::string_view case_id : {"spectrum", "afterglow"}) {
        Gallery_Plot_Session session;
        auto state = response_json(session.handle(gallery_request(
            Gallery_Request_Kind::Open, open_message(case_id, "manual"))));
        ASSERT_EQ(state.at("type"), "case_state");
        state = patch(session, "frequency_axis",
                      {{"orientation", "Vertical"},
                       {"coordinates", {{"origin", 108'000'000.0},
                                        {"target", 88'000'000.0}}}});
        ASSERT_EQ(state.at("type"), "case_state");
        state = patch(session, "value_axis",
                      {{"orientation", "Horizontal"},
                       {"coordinates", {{"origin", -20.0}, {"target", -120.0}}}});
        ASSERT_EQ(state.at("type"), "case_state");
        state = patch(session, case_id,
                      {{"partition_mode", "Fixed"}, {"partition_count", 4}});
        ASSERT_EQ(state.at("type"), "case_state");
        EXPECT_EQ(resource_data(state, "frequency_axis").at("orientation"), "Vertical");
        EXPECT_EQ(resource_data(state, "value_axis").at("orientation"), "Horizontal");
        EXPECT_EQ(resource_data(state, case_id).at("partition_count"), 4);
    }

    Gallery_Plot_Session waterfall;
    auto state = response_json(waterfall.handle(gallery_request(
        Gallery_Request_Kind::Open, open_message("waterfall", "manual"))));
    ASSERT_EQ(state.at("type"), "case_state");
    state = patch(waterfall, "frequency_axis",
                  {{"orientation", "Vertical"},
                   {"coordinates", {{"origin", 108'000'000.0},
                                    {"target", 88'000'000.0}}}});
    ASSERT_EQ(state.at("type"), "case_state");
    state = patch(waterfall, "time_axis",
                  {{"orientation", "Horizontal"}, {"newest_at_start", true}});
    ASSERT_EQ(state.at("type"), "case_state");
    state = patch(waterfall, "waterfall",
                  {{"partition_mode", "Fixed"}, {"partition_count", 4}});
    ASSERT_EQ(state.at("type"), "case_state");
    EXPECT_EQ(resource_data(state, "frequency_axis").at("orientation"), "Vertical");
    EXPECT_EQ(resource_data(state, "time_axis").at("orientation"), "Horizontal");
    EXPECT_TRUE(resource_data(state, "time_axis").at("newest_at_start"));
    EXPECT_EQ(resource_data(state, "waterfall").at("partition_count"), 4);

    for (const auto& [frequency_orientation, time_orientation, newest_at_start] :
         std::array{
             std::tuple{"Horizontal", "Vertical", false},
             std::tuple{"Horizontal", "Vertical", true},
             std::tuple{"Vertical", "Horizontal", false},
             std::tuple{"Vertical", "Horizontal", true}}) {
        state = patch(waterfall, "frequency_axis",
                      {{"orientation", frequency_orientation}});
        ASSERT_EQ(state.at("type"), "case_state");
        state = patch(waterfall, "time_axis",
                      {{"orientation", time_orientation},
                       {"newest_at_start", newest_at_start}});
        ASSERT_EQ(state.at("type"), "case_state");
        EXPECT_EQ(resource_data(state, "frequency_axis").at("orientation"),
                  frequency_orientation);
        EXPECT_EQ(resource_data(state, "time_axis").at("orientation"),
                  time_orientation);
        EXPECT_EQ(resource_data(state, "time_axis").at("newest_at_start"),
                  newest_at_start);
    }
}

TEST(RenderiveWebGallery, EveryPublishedActionDispatchesToItsCanvasAndFrameMode) {
    constexpr std::array<std::string_view, 8> cases{
        "axis_lab", "spectrum", "afterglow", "sweep_spectrum",
        "waterfall", "frequency_trace", "selection_overlay", "constellation"
    };
    constexpr std::array<std::string_view, 3> modes{"manual", "low_latency", "playback"};
    std::size_t dispatched_action_instances{};
    for (const auto case_id : cases) {
        for (const auto mode : modes) {
            Gallery_Plot_Session catalog_session;
            const auto opened = response_json(catalog_session.handle(gallery_request(
                Gallery_Request_Kind::Open, open_message(case_id, mode))));
            ASSERT_EQ(opened.at("type"), "case_state");
            const auto actions = opened.at("actions").at("data");
            std::set<std::string> ids;
            for (const auto& action : actions) {
                ++dispatched_action_instances;
                const std::string id = action.at("id");
                SCOPED_TRACE(std::string(case_id) + "/" + std::string(mode) + "/" + id);
                EXPECT_TRUE(ids.insert(id).second);
                EXPECT_FALSE(action.at("api").get<std::string>().empty());
                EXPECT_FALSE(action.at("group").get<std::string>().empty());

                Gallery_Plot_Session session;
                ASSERT_EQ(response_json(session.handle(gallery_request(
                    Gallery_Request_Kind::Open, open_message(case_id, mode)))).at("type"),
                          "case_state");
                std::optional<double> argument;
                if (!action.at("argument_input").get<std::string>().empty())
                    argument = action.at("argument_default").get<double>();
                const auto result = invoke_action(session, id, argument);
                EXPECT_EQ(result.at("type"), "case_state");
                EXPECT_EQ(result.at("case").at("id").get<std::string>(), case_id);
                EXPECT_EQ(result.at("frame_mode").at("id").get<std::string>(), mode);
                EXPECT_TRUE(result.at("telemetry").contains("kernel_observer"));
            }
        }
    }
    EXPECT_GT(dispatched_action_instances, 150U);
}

TEST(RenderiveWebGallery, CanvasSpecificActionsProduceObservableStateChanges) {
    {
        Gallery_Plot_Session session;
        const auto opened = response_json(session.handle(gallery_request(
            Gallery_Request_Kind::Open, open_message("axis_lab", "manual"))));
        const auto before = opened.at("telemetry").at("axis_lab").at("time_axis_point_count")
                                .get<std::size_t>();
        const auto after = invoke_action(session, "append_time").at("telemetry");
        EXPECT_EQ(after.at("axis_lab").at("time_axis_point_count").get<std::size_t>(),
                  before + 1);
        EXPECT_NE(after.at("last_action_result").get<std::string>().find("tick="),
                  std::string::npos);
    }
    {
        Gallery_Plot_Session session;
        ASSERT_EQ(response_json(session.handle(gallery_request(
            Gallery_Request_Kind::Open, open_message("spectrum", "manual")))).at("type"),
                  "case_state");
        auto telemetry = invoke_action(session, "add_line_marker", 96e6).at("telemetry");
        EXPECT_EQ(telemetry.at("spectrum").at("selectable_line_markers"), 1);
        telemetry = invoke_action(session, "select_marker", 0).at("telemetry");
        EXPECT_EQ(telemetry.at("spectrum").at("selected_marker_index"), 0);
        telemetry = invoke_action(session, "set_marker_frequency", 99e6).at("telemetry");
        EXPECT_DOUBLE_EQ(telemetry.at("spectrum").at("selected_marker_frequency"), 99e6);
        telemetry = invoke_action(session, "clear_markers").at("telemetry");
        EXPECT_EQ(telemetry.at("spectrum").at("selectable_line_markers"), 0);
        EXPECT_EQ(telemetry.at("spectrum").at("selected_marker_index"), -1);
    }
    {
        Gallery_Plot_Session session;
        const auto opened = response_json(session.handle(gallery_request(
            Gallery_Request_Kind::Open, open_message("waterfall", "manual"))));
        const auto before_rows = opened.at("telemetry").at("waterfall").at("row_count")
                                     .get<std::size_t>();
        auto telemetry = invoke_action(session, "append_row").at("telemetry");
        EXPECT_EQ(telemetry.at("waterfall").at("row_count").get<std::size_t>(),
                  before_rows + 1);
        telemetry = invoke_action(session, "append_tick_row").at("telemetry");
        EXPECT_EQ(telemetry.at("waterfall").at("row_count").get<std::size_t>(),
                  before_rows + 2);
    }
    {
        Gallery_Plot_Session session;
        const auto opened = response_json(session.handle(gallery_request(
            Gallery_Request_Kind::Open, open_message("afterglow", "manual"))));
        const auto before = opened.at("telemetry").at("afterglow")
                                .at("history_frame_count").get<std::size_t>();
        const auto telemetry = invoke_action(session, "append_spectrum").at("telemetry");
        EXPECT_EQ(telemetry.at("afterglow").at("history_frame_count").get<std::size_t>(),
                  before + 1);
    }
    {
        Gallery_Plot_Session session;
        const auto opened = response_json(session.handle(gallery_request(
            Gallery_Request_Kind::Open, open_message("sweep_spectrum", "manual"))));
        const auto before = opened.at("telemetry").at("sweep_spectrum")
                                .at("stored_block_count").get<std::size_t>();
        const auto telemetry = invoke_action(session, "append_block").at("telemetry");
        EXPECT_EQ(telemetry.at("sweep_spectrum").at("stored_block_count").get<std::size_t>(),
                  before + 1);
    }
    {
        Gallery_Plot_Session session;
        const auto opened = response_json(session.handle(gallery_request(
            Gallery_Request_Kind::Open, open_message("frequency_trace", "manual"))));
        const auto before = opened.at("telemetry").at("frequency_trace")
                                .at("sample_count").get<std::size_t>();
        auto telemetry = invoke_action(session, "append_sample").at("telemetry");
        EXPECT_EQ(telemetry.at("frequency_trace").at("sample_count").get<std::size_t>(),
                  before + 1);
        telemetry = invoke_action(session, "append_tick_sample").at("telemetry");
        EXPECT_EQ(telemetry.at("frequency_trace").at("sample_count").get<std::size_t>(),
                  before + 2);
    }
    {
        Gallery_Plot_Session session;
        const auto opened = response_json(session.handle(gallery_request(
            Gallery_Request_Kind::Open, open_message("constellation", "manual"))));
        const auto before = opened.at("telemetry").at("constellation")
                                .at("point_count").get<std::size_t>();
        const auto telemetry = invoke_action(session, "append_points").at("telemetry");
        EXPECT_EQ(telemetry.at("constellation").at("point_count").get<std::size_t>(),
                  before + 24);
    }
}

TEST(RenderiveWebGallery, CommonControlsAndViewLifecycleProduceObservableEffects) {
    Gallery_Plot_Session session;
    ASSERT_EQ(response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Open, open_message("spectrum", "manual")))).at("type"),
              "case_state");

    const auto background_state = patch_controls(session, {{"background_color", "#123456"}});
    ASSERT_EQ(background_state.at("type"), "case_state") << background_state.dump();
    EXPECT_EQ(background_state.at("controls").at("resources").at(0).at("data")
                  .at("background_color"), "#123456");
    ASSERT_EQ(invoke_action(session, "mode_cycle").at("type"), "case_state");
    const auto background_frame = session.handle(Frame_Request{});
    ASSERT_TRUE(background_frame.has_value());
    ASSERT_EQ(background_frame->type, Web_Response_Type::Pixels);
    ASSERT_GE(background_frame->payload.size(), pixel_frame_header_size + 4);
    const auto* corner = reinterpret_cast<const std::uint8_t*>(
        background_frame->payload.data() + pixel_frame_header_size);
    EXPECT_EQ(corner[0], 0x12);
    EXPECT_EQ(corner[1], 0x34);
    EXPECT_EQ(corner[2], 0x56);
    EXPECT_EQ(corner[3], 0xff);

    auto state = patch_controls(session, {
        {"performance_overlay", true},
        {"renderable_visible", false},
        {"cache_mode", "Direct"},
        {"object_name", "Observable_Spectrum"}
    });
    auto telemetry = state.at("telemetry");
    EXPECT_TRUE(telemetry.at("performance_overlay_enabled").get<bool>());
    EXPECT_FALSE(telemetry.at("renderable").at("visible").get<bool>());
    EXPECT_EQ(telemetry.at("renderable").at("cache_mode"), "Direct");
    EXPECT_EQ(telemetry.at("renderable").at("object_name"), "Observable_Spectrum");

    state = invoke_action(session, "toggle_view");
    EXPECT_FALSE(state.at("telemetry").at("view_active").get<bool>());
    EXPECT_FALSE(session.handle(Frame_Request{}).has_value());
    state = invoke_action(session, "toggle_view");
    EXPECT_TRUE(state.at("telemetry").at("view_active").get<bool>());
    ASSERT_EQ(invoke_action(session, "mode_cycle").at("type"), "case_state");
    EXPECT_TRUE(session.handle(Frame_Request{}).has_value());
}

TEST(RenderiveWebGallery, SelectionOverlayPointerLifecycleCreatesAndClearsRegion) {
    Gallery_Plot_Session session;
    ASSERT_EQ(response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Open, open_message("selection_overlay", "manual")))).at("type"),
              "case_state");

    const auto resize = Web_Event_Adapter::decode(
        R"({"category":"event","type":"resize","width":560,"height":320})");
    ASSERT_TRUE(resize.has_value());
    EXPECT_FALSE(session.handle(*resize).has_value());
    constexpr std::array<std::string_view, 3> drag{
        R"({"category":"event","type":"pointer_press","x":120,"y":90,"button":"left","buttons":1,"modifiers":0})",
        R"({"category":"event","type":"pointer_move","x":300,"y":210,"button":"none","buttons":1,"modifiers":0})",
        R"({"category":"event","type":"pointer_release","x":300,"y":210,"button":"left","buttons":0,"modifiers":0})"
    };
    for (const auto encoded : drag) {
        const auto event = Web_Event_Adapter::decode(encoded);
        ASSERT_TRUE(event.has_value());
        EXPECT_FALSE(session.handle(*event).has_value());
    }

    EXPECT_EQ(observe_telemetry(session).at("selection_regions"), 1);
    EXPECT_EQ(invoke_action(session, "clear_selection").at("telemetry")
                  .at("selection_regions"), 0);
}

TEST(RenderiveWebGallery, EveryCanvasAcceptsTheCompleteWebEventSetAndStillRendersPixels) {
    constexpr std::array<std::string_view, 8> cases{
        "axis_lab", "spectrum", "afterglow", "sweep_spectrum",
        "waterfall", "frequency_trace", "selection_overlay", "constellation"
    };
    constexpr std::array<std::string_view, 3> modes{"manual", "low_latency", "playback"};
    constexpr std::array<std::string_view, 10> interactive_events{
        R"({"category":"event","type":"show"})",
        R"({"category":"event","type":"pointer_move","x":100,"y":100,"button":"none","buttons":0,"modifiers":0})",
        R"({"category":"event","type":"pointer_press","x":100,"y":100,"button":"left","buttons":1,"modifiers":1})",
        R"({"category":"event","type":"pointer_move","x":180,"y":160,"button":"none","buttons":1,"modifiers":1})",
        R"({"category":"event","type":"pointer_release","x":180,"y":160,"button":"left","buttons":0,"modifiers":1})",
        R"({"category":"event","type":"wheel","x":160,"y":120,"pixelDeltaX":0,"pixelDeltaY":-24,"angleDeltaX":0,"angleDeltaY":192,"buttons":0,"modifiers":2})",
        R"({"category":"event","type":"key_press","key":"Escape","nativeKey":27,"repeat":false,"modifiers":0})",
        R"({"category":"event","type":"key_release","key":"Escape","nativeKey":27,"repeat":false,"modifiers":0})",
        R"({"category":"event","type":"leave"})",
        R"({"category":"event","type":"hide"})"
    };

    for (const auto case_id : cases) {
        for (const auto mode : modes) {
            SCOPED_TRACE(std::string(case_id) + "/" + std::string(mode));
            Gallery_Plot_Session session(mode == "low_latency");
            ASSERT_EQ(response_json(session.handle(gallery_request(
                Gallery_Request_Kind::Open, open_message(case_id, mode)))).at("type"),
                      "case_state");
            const auto resize = Web_Event_Adapter::decode(
                R"({"category":"event","type":"resize","width":420,"height":260})");
            ASSERT_TRUE(resize.has_value());
            EXPECT_FALSE(session.handle(*resize).has_value());

            for (const auto encoded : interactive_events) {
                const auto event = Web_Event_Adapter::decode(encoded);
                ASSERT_TRUE(event.has_value()) << encoded;
                EXPECT_FALSE(session.handle(*event).has_value()) << encoded;
            }
            EXPECT_FALSE(session.handle(Frame_Request{}).has_value());

            const auto show = Web_Event_Adapter::decode(
                R"({"category":"event","type":"show"})");
            ASSERT_TRUE(show.has_value());
            EXPECT_FALSE(session.handle(*show).has_value());
            const auto frame = wait_for_pixel_frame(session, 420, 260);
            ASSERT_TRUE(frame.has_value());
            ASSERT_EQ(frame->type, Web_Response_Type::Pixels);
            EXPECT_EQ(frame->payload.substr(0, 4), "RVP1");
            EXPECT_EQ(read_u32_le(frame->payload, 4), 420U);
            EXPECT_EQ(read_u32_le(frame->payload, 8), 260U);
            const auto telemetry = observe_telemetry(session);
            EXPECT_TRUE(telemetry.at("view_active").get<bool>());
            EXPECT_EQ(telemetry.at("viewport").at("width"), 420);
            EXPECT_EQ(telemetry.at("viewport").at("height"), 260);
        }
    }
}

TEST(RenderiveWebGallery, InvalidInteractiveEventsAreRejectedBeforeReachingCore2) {
    constexpr std::array<std::string_view, 8> invalid{
        R"({"category":"command","type":"pointer_move","x":1,"y":2})",
        R"({"category":"event","type":"pointer_move","x":1})",
        R"({"category":"event","type":"pointer_press","x":"bad","y":2})",
        R"({"category":"event","type":"wheel","x":1,"y":2})",
        R"({"category":"event","type":"resize","width":"bad","height":200})",
        R"({"category":"event","type":"control","control":"mode","value":"INVALID"})",
        R"({"category":"event","type":"unknown"})",
        "not-json"
    };
    for (const auto encoded : invalid)
        EXPECT_FALSE(Web_Event_Adapter::decode(encoded).has_value()) << encoded;
}

TEST(RenderiveWebGallery, EveryIndependentCore2CanvasBuildsAndRenders) {
    constexpr std::array<std::string_view, 8> cases{
        "axis_lab", "spectrum", "afterglow", "sweep_spectrum",
        "waterfall", "frequency_trace", "selection_overlay", "constellation"
    };
    constexpr std::array<std::string_view, 3> modes{"manual", "low_latency", "playback"};
    for (const std::string_view mode : modes) {
        for (const std::string_view case_id : cases) {
            Gallery_Plot_Session session(mode == "low_latency");
            const auto opened = session.handle(gallery_request(
                Gallery_Request_Kind::Open, open_message(case_id, mode)));
            ASSERT_TRUE(opened.has_value()) << case_id;
            ASSERT_EQ(opened->type, Web_Response_Type::Json) << case_id;
            const auto state = parse_json(opened->payload);
            EXPECT_EQ(state.at("type"), "case_state") << case_id;
            EXPECT_EQ(state.at("case").at("id").get<std::string>(), std::string(case_id))
                << case_id;
            EXPECT_EQ(state.at("frame_mode").at("id"), std::string(mode)) << case_id;
            const auto& resources = state.at("controls").at("resources");
            ASSERT_FALSE(resources.empty()) << case_id;
            for (const auto& resource : resources) {
                EXPECT_EQ(resource.at("descriptor").at("protocol"),
                          "adminive.resource") << case_id;
                EXPECT_EQ(resource.at("view").at("protocol"),
                          "adminive.view") << case_id;
            }
            EXPECT_FALSE(session.handle(Viewport_Resize{{480, 280}}).has_value()) << case_id;
            const auto frame = wait_for_pixel_frame(session, 480, 280);
            ASSERT_TRUE(frame.has_value()) << case_id;
            ASSERT_EQ(frame->type, Web_Response_Type::Pixels) << case_id;
            EXPECT_EQ(frame->payload.substr(0, 4), "RVP1") << case_id;
            EXPECT_EQ(read_u32_le(frame->payload, 4), 480U) << case_id;
            EXPECT_EQ(read_u32_le(frame->payload, 8), 280U) << case_id;
            const auto observed = session.handle(gallery_request(
                Gallery_Request_Kind::Observe,
                R"({"category":"event","type":"gallery_observe"})"));
            ASSERT_TRUE(observed.has_value()) << case_id;
            const auto telemetry = parse_json(observed->payload);
            EXPECT_EQ(telemetry.at("type"), "observer_state") << case_id;
            EXPECT_EQ(telemetry.at("frame_mode").at("id"), std::string(mode)) << case_id;
            EXPECT_TRUE(telemetry.at("telemetry").contains("kernel_observer")) << case_id;
            EXPECT_TRUE(telemetry.at("telemetry").contains("performance")) << case_id;
            EXPECT_TRUE(telemetry.at("telemetry").contains("data_shape")) << case_id;
            EXPECT_GT(telemetry.at("telemetry").at("data_shape").at("rendered_elements")
                          .get<std::size_t>(), 0U) << case_id;
        }
    }
}

TEST(RenderiveWebGallery, ActionsMutateLiveCore2ObjectsAndReturnTelemetry) {
    Gallery_Plot_Session session;
    ASSERT_TRUE(session.handle(gallery_request(
        Gallery_Request_Kind::Open,
        open_message("spectrum", "low_latency"))));

    const auto add = session.handle(gallery_request(
        Gallery_Request_Kind::Action,
        R"({"category":"event","type":"gallery_action","action":"add_line_marker","argument":97000000})"));
    ASSERT_TRUE(add.has_value());
    const auto add_state = parse_json(add->payload);
    EXPECT_EQ(add_state.at("type"), "case_state");
    EXPECT_EQ(add_state.at("telemetry").at("spectrum").at("selectable_line_markers"), 1);

    const auto patch = session.handle(gallery_request(
        Gallery_Request_Kind::Patch,
        R"({"category":"event","type":"gallery_patch","target":"spectrum","patch":{"interpolation_mode":"Step_Left","max_hold_visible":true}})"));
    ASSERT_TRUE(patch.has_value());
    EXPECT_EQ(parse_json(patch->payload).at("type"), "case_state");

}

TEST(RenderiveWebGallery, ThreeFrameStrategiesExposeTheirRealActionsAndObservers) {
    const auto action = [](Gallery_Plot_Session& session, std::string_view id,
                           std::optional<int> argument = std::nullopt) {
        nlohmann::json request{{"category", "event"}, {"type", "gallery_action"},
                               {"action", id}};
        if (argument)
            request["argument"] = *argument;
        return session.handle(gallery_request(Gallery_Request_Kind::Action, request.dump()));
    };
    const auto observe = [](Gallery_Plot_Session& session) {
        const auto response = session.handle(gallery_request(
            Gallery_Request_Kind::Observe,
            R"({"category":"event","type":"gallery_observe"})"));
        EXPECT_TRUE(response.has_value());
        return parse_json(response->payload).at("telemetry");
    };

    Gallery_Plot_Session manual;
    ASSERT_TRUE(manual.handle(gallery_request(
        Gallery_Request_Kind::Open, open_message("spectrum", "manual"))));
    ASSERT_TRUE(action(manual, "mode_prepare"));
    auto manual_observer = observe(manual).at("kernel_observer");
    EXPECT_EQ(manual_observer.at("last_event"), "prepared");
    EXPECT_EQ(manual_observer.at("pending_frame_count"), 1);
    ASSERT_TRUE(action(manual, "mode_refresh"));
    ASSERT_TRUE(action(manual, "mode_render"));
    manual_observer = observe(manual).at("kernel_observer");
    EXPECT_EQ(manual_observer.at("last_event"), "rendered");
    EXPECT_GE(manual_observer.at("consumed_frame_count").get<std::uint64_t>(), 2U);

    Gallery_Plot_Session low_latency;
    ASSERT_TRUE(low_latency.handle(gallery_request(
        Gallery_Request_Kind::Open, open_message("spectrum", "low_latency"))));
    const auto high_frequency = low_latency.handle(gallery_request(
        Gallery_Request_Kind::Patch,
        R"({"category":"event","type":"gallery_patch","target":"scene","patch":{"max_render_fps":1000000000}})"));
    ASSERT_TRUE(high_frequency.has_value());
    ASSERT_EQ(parse_json(high_frequency->payload).at("type"), "case_state");
    ASSERT_TRUE(low_latency.handle(Frame_Request{}));
    const auto low_telemetry = observe(low_latency);
    EXPECT_EQ(low_telemetry.at("frame_mode"), "low_latency");
    const auto& low_observer = low_telemetry.at("kernel_observer");
    EXPECT_GT(low_observer.at("observation_count").get<std::uint64_t>(), 0U);
    EXPECT_FALSE(low_telemetry.contains("render_fps"));
    EXPECT_FALSE(low_telemetry.contains("low_latency_limit"));
    EXPECT_NE(low_observer.at("limit_state"), "consumer_limited");
    EXPECT_NE(low_observer.at("limit_state"), "frequency_limited");
    EXPECT_TRUE(low_observer.at("limit_state") == "paint_limited" ||
                low_observer.at("limit_state") == "render_limited");
    EXPECT_EQ(low_observer.at("target_interval_ns"), 1);
    EXPECT_EQ(low_observer.at("consumer_interval_ns"), 0);
    EXPECT_GT(low_observer.at("bottleneck_duration_ns").get<std::uint64_t>(), 1U);
    EXPECT_TRUE(low_observer.contains("paint_lease_wait_ns"));
    EXPECT_TRUE(low_observer.contains("render_finish_state_wait_ns"));
    constexpr std::array<std::string_view, 33> observer_fields{
        "mode", "last_event", "limit_state", "frequency_hz", "observation_count",
        "produced_frame_count", "consumed_frame_count", "dropped_frame_count",
        "failed_operation_count", "pending_frame_count", "latest_sequence",
        "paint_duration_ns", "render_duration_ns", "target_interval_ns", "frequency_limit_enabled",
        "consumer_feedback_enabled", "bottleneck_duration_ns", "consumer_sample_interval_ns",
        "consumer_smoothed_interval_ns", "consumer_variation_ns", "consumer_safety_interval_ns", "consumer_interval_ns",
        "next_refresh_interval_ns", "paint_lease_wait_ns",
        "paint_state_wait_ns", "publish_state_wait_ns", "ready_wait_ns",
        "frame_age_at_render_ns", "render_lease_wait_ns", "render_state_wait_ns",
        "render_finish_state_wait_ns", "queue_wait_ns", "end_to_end_ns"
    };
    for (const auto field : observer_fields)
        EXPECT_TRUE(low_observer.contains(field)) << field;

    Gallery_Plot_Session playback;
    ASSERT_TRUE(playback.handle(gallery_request(
        Gallery_Request_Kind::Open, open_message("spectrum", "playback"))));
    ASSERT_TRUE(action(playback, "mode_enqueue_burst", 5));
    auto playback_observer = observe(playback).at("kernel_observer");
    EXPECT_EQ(playback_observer.at("last_event"), "enqueued");
    EXPECT_EQ(playback_observer.at("pending_frame_count"), 5);
    ASSERT_TRUE(action(playback, "mode_dequeue"));
    playback_observer = observe(playback).at("kernel_observer");
    EXPECT_EQ(playback_observer.at("last_event"), "rendered");
    EXPECT_EQ(playback_observer.at("pending_frame_count"), 4);
    EXPECT_GT(playback_observer.at("queue_wait_ns").get<std::uint64_t>(), 0U);
}

TEST(RenderiveWebGallery, ManualStrategyRunsOnlyExplicitPrepareRefreshRenderCycles) {
    Gallery_Plot_Session session(true);
    const auto opened = response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Open, open_message("spectrum", "manual"))));
    ASSERT_EQ(opened.at("type"), "case_state");
    const auto baseline = opened.at("telemetry").at("kernel_observer");
    EXPECT_EQ(baseline.at("mode"), "manual");
    EXPECT_EQ(baseline.at("limit_state"), "not_applicable");

    std::this_thread::sleep_for(std::chrono::milliseconds(80));
    auto observer = observe_telemetry(session).at("kernel_observer");
    EXPECT_EQ(observer.at("latest_sequence"), baseline.at("latest_sequence"));
    EXPECT_EQ(observer.at("produced_frame_count"), baseline.at("produced_frame_count"));
    EXPECT_EQ(observer.at("consumed_frame_count"), baseline.at("consumed_frame_count"));

    auto state = invoke_action(session, "mode_prepare");
    observer = state.at("telemetry").at("kernel_observer");
    EXPECT_EQ(observer.at("last_event"), "prepared");
    EXPECT_EQ(observer.at("pending_frame_count"), 1);
    EXPECT_EQ(observer.at("produced_frame_count").get<std::uint64_t>(),
              baseline.at("produced_frame_count").get<std::uint64_t>() + 1);

    state = invoke_action(session, "mode_refresh");
    observer = state.at("telemetry").at("kernel_observer");
    EXPECT_EQ(observer.at("last_event"), "refresh_succeeded");
    EXPECT_EQ(observer.at("pending_frame_count"), 0);
    EXPECT_EQ(observer.at("consumed_frame_count"), baseline.at("consumed_frame_count"));

    state = invoke_action(session, "mode_render");
    observer = state.at("telemetry").at("kernel_observer");
    EXPECT_EQ(observer.at("last_event"), "rendered");
    EXPECT_EQ(observer.at("pending_frame_count"), 0);
    EXPECT_EQ(observer.at("consumed_frame_count").get<std::uint64_t>(),
              baseline.at("consumed_frame_count").get<std::uint64_t>() + 1);

    ASSERT_EQ(invoke_action(session, "mode_prepare").at("type"), "case_state");
    state = invoke_action(session, "mode_discard");
    observer = state.at("telemetry").at("kernel_observer");
    EXPECT_EQ(observer.at("last_event"), "manually_discarded");
    EXPECT_EQ(observer.at("pending_frame_count"), 0);
    EXPECT_EQ(observer.at("dropped_frame_count").get<std::uint64_t>(),
              baseline.at("dropped_frame_count").get<std::uint64_t>() + 1);

    const auto failed_before = observer.at("failed_operation_count").get<std::uint64_t>();
    state = invoke_action(session, "mode_refresh");
    observer = state.at("telemetry").at("kernel_observer");
    EXPECT_EQ(observer.at("last_event"), "refresh_failed");
    EXPECT_EQ(observer.at("failed_operation_count").get<std::uint64_t>(), failed_before + 1);

    const auto sequence_after_actions = observer.at("latest_sequence");
    std::this_thread::sleep_for(std::chrono::milliseconds(80));
    EXPECT_EQ(observe_telemetry(session).at("kernel_observer").at("latest_sequence"),
              sequence_after_actions);
}

TEST(RenderiveWebGallery, LowLatencyStrategyKeepsForegroundResponsiveUnderAggressiveFrequency) {
    Gallery_Plot_Session session(true);
    ASSERT_EQ(response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Open, open_message("spectrum", "low_latency")))).at("type"),
              "case_state");
    ASSERT_EQ(response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Patch,
        R"({"category":"event","type":"gallery_patch","target":"scene","patch":{"max_render_fps":1000}})"))).at("type"),
              "case_state");

    ASSERT_TRUE(wait_for_condition([&session] {
        return successful_render_count(session) >= 4;
    }));
    std::string first_pixels;
    std::string last_pixels;
    for (int iteration = 0; iteration < 12; ++iteration) {
        const auto started = std::chrono::steady_clock::now();
        const auto frame = session.handle(Frame_Request{});
        ASSERT_TRUE(frame.has_value()) << iteration;
        ASSERT_EQ(frame->type, Web_Response_Type::Pixels) << iteration;
        const auto telemetry = observe_telemetry(session);
        EXPECT_LT(std::chrono::steady_clock::now() - started,
                  std::chrono::milliseconds(250)) << iteration;
        EXPECT_EQ(telemetry.at("kernel_observer").at("frequency_hz"), 1000.0);
        if (iteration == 0)
            first_pixels = frame->payload;
        last_pixels = frame->payload;
        std::this_thread::sleep_for(std::chrono::milliseconds(3));
    }
    EXPECT_NE(first_pixels, last_pixels);

    const auto telemetry = observe_telemetry(session);
    const auto& observer = telemetry.at("kernel_observer");
    EXPECT_EQ(observer.at("target_interval_ns"), 1'000'000);
    EXPECT_GT(observer.at("latest_sequence").get<std::uint64_t>(), 4U);
    EXPECT_GT(telemetry.at("performance").at("successful_render_count").get<std::uint64_t>(), 4U);
    EXPECT_TRUE(observer.at("limit_state") == "frequency_limited" ||
                observer.at("limit_state") == "paint_limited" ||
                observer.at("limit_state") == "render_limited" ||
                observer.at("limit_state") == "consumer_limited");
}

TEST(RenderiveWebGallery, FrequencyLimitCannotBeBypassedByForegroundRescheduleEvents) {
    Gallery_Plot_Session session(true);
    ASSERT_EQ(response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Open, open_message("spectrum", "low_latency")))).at("type"),
              "case_state");
    const auto patched = patch_controls(session, {
        {"frequency_limit_enabled", true},
        {"max_render_fps", 30.0},
        {"feedback", {{"enabled", false}}}
    });
    ASSERT_EQ(patched.at("type"), "case_state");
    const auto& patched_observer = patched.at("telemetry").at("kernel_observer");
    EXPECT_TRUE(patched_observer.at("frequency_limit_enabled").get<bool>());
    EXPECT_FALSE(patched_observer.at("consumer_feedback_enabled").get<bool>());
    EXPECT_EQ(patched_observer.at("target_interval_ns"), 33'333'333);
    ASSERT_TRUE(wait_for_condition([&session] {
        return successful_render_count(session) >= 3;
    }));
    const auto pointer = Web_Event_Adapter::decode(
        R"({"category":"event","type":"pointer_move","x":320,"y":180,"button":"none","buttons":0,"modifiers":0})");
    ASSERT_TRUE(pointer.has_value());
    const auto before = successful_render_count(session);
    const auto started = std::chrono::steady_clock::now();
    const auto deadline = started + std::chrono::milliseconds(450);
    while (std::chrono::steady_clock::now() < deadline) {
        EXPECT_FALSE(session.handle(*pointer).has_value());
        std::this_thread::sleep_for(std::chrono::milliseconds(2));
    }
    const auto elapsed = std::chrono::steady_clock::now() - started;
    const auto after = successful_render_count(session);
    const auto rendered = after - before;
    EXPECT_GT(rendered, 0U);
    EXPECT_LE(rendered, 16U);
    const auto telemetry = observe_telemetry(session);
    const auto& observer = telemetry.at("kernel_observer");
    EXPECT_EQ(observer.at("target_interval_ns"), 33'333'333);
    EXPECT_GE(observer.at("next_refresh_interval_ns").get<std::uint64_t>(), 33'333'333U);
    EXPECT_GE(elapsed, std::chrono::milliseconds(450));
}

TEST(RenderiveWebGallery, ConsumerFeedbackCannotBeBypassedByForegroundRescheduleEvents) {
    Gallery_Plot_Session session(true);
    ASSERT_EQ(response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Open, open_message("spectrum", "low_latency")))).at("type"),
              "case_state");
    ASSERT_EQ(patch_controls(session, {
        {"frequency_limit_enabled", false},
        {"feedback", {{"enabled", true}}}
    }).at("type"), "case_state");
    auto observed = response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Observe,
        R"({"category":"event","type":"gallery_observe","client_metrics":{"transport_fps":25,"presentation_fps":25,"websocket_buffered_bytes":0,"changed_pixel_frames":1,"duplicate_pixel_frames":0,"frame_request_timeout_count":0,"frame_round_trip_ms":5,"display_interval_ms":40,"overwritten_pixel_frames":0,"last_pixel_receive_age_ms":1,"last_pixel_change_age_ms":1}})")));
    auto observer = observed.at("telemetry").at("kernel_observer");
    EXPECT_FALSE(observer.at("frequency_limit_enabled").get<bool>());
    EXPECT_TRUE(observer.at("consumer_feedback_enabled").get<bool>());
    EXPECT_EQ(observer.at("consumer_interval_ns"), 40'000'000);
    ASSERT_TRUE(wait_for_condition([&session] {
        return successful_render_count(session) >= 3;
    }));
    const auto pointer = Web_Event_Adapter::decode(
        R"({"category":"event","type":"pointer_move","x":320,"y":180,"button":"none","buttons":0,"modifiers":0})");
    ASSERT_TRUE(pointer.has_value());
    const auto before = successful_render_count(session);
    const auto started = std::chrono::steady_clock::now();
    const auto deadline = started + std::chrono::milliseconds(450);
    while (std::chrono::steady_clock::now() < deadline) {
        EXPECT_FALSE(session.handle(*pointer).has_value());
        std::this_thread::sleep_for(std::chrono::milliseconds(2));
    }
    const auto after = successful_render_count(session);
    const auto rendered = after - before;
    EXPECT_GT(rendered, 0U);
    EXPECT_LE(rendered, 14U);
    observed = observe_telemetry(session);
    observer = observed.at("kernel_observer");
    EXPECT_EQ(observer.at("consumer_interval_ns"), 40'000'000);
    EXPECT_GE(observer.at("next_refresh_interval_ns").get<std::uint64_t>(), 40'000'000U);
}

TEST(RenderiveWebGallery, WebRuntimeMetricsPublishKernelSmoothedConsumerFeedbackWithoutTransportFpsCap) {
    Gallery_Plot_Session session(true);
    ASSERT_EQ(response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Open, open_message("spectrum", "low_latency")))).at("type"),
              "case_state");
    ASSERT_EQ(response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Patch,
        R"({"category":"event","type":"gallery_patch","target":"scene","patch":{"max_render_fps":1000000000}})"))).at("type"),
              "case_state");
    auto observed = response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Observe,
        R"({"category":"event","type":"gallery_observe","client_metrics":{"transport_fps":50,"presentation_fps":50,"websocket_buffered_bytes":0,"changed_pixel_frames":10,"duplicate_pixel_frames":0,"frame_request_timeout_count":0,"frame_round_trip_ms":80,"display_interval_ms":20,"overwritten_pixel_frames":0,"last_pixel_receive_age_ms":1,"last_pixel_change_age_ms":1}})")));
    auto observer = observed.at("telemetry").at("kernel_observer");
    auto feedback = observed.at("telemetry").at("consumer_feedback");
    EXPECT_TRUE(observer.at("frequency_limit_enabled").get<bool>());
    EXPECT_TRUE(observer.at("consumer_feedback_enabled").get<bool>());
    EXPECT_EQ(observer.at("consumer_sample_interval_ns"), 20'000'000);
    EXPECT_EQ(observer.at("consumer_smoothed_interval_ns"), 20'000'000);
    EXPECT_EQ(observer.at("consumer_variation_ns"), 0);
    EXPECT_EQ(observer.at("consumer_safety_interval_ns"), 20'000'000);
    EXPECT_EQ(observer.at("consumer_interval_ns"), 20'000'000);
    EXPECT_EQ(observer.at("limit_state"), "consumer_limited");
    EXPECT_EQ(feedback.at("source"), "presentation");
    observed = response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Observe,
        R"({"category":"event","type":"gallery_observe","client_metrics":{"transport_fps":45,"presentation_fps":45,"websocket_buffered_bytes":0,"changed_pixel_frames":20,"duplicate_pixel_frames":0,"frame_request_timeout_count":0,"frame_round_trip_ms":4,"display_interval_ms":22,"overwritten_pixel_frames":0,"last_pixel_receive_age_ms":1,"last_pixel_change_age_ms":1}})")));
    observer = observed.at("telemetry").at("kernel_observer");
    EXPECT_EQ(observer.at("consumer_sample_interval_ns"), 22'000'000);
    EXPECT_EQ(observer.at("consumer_smoothed_interval_ns"), 21'000'000);
    EXPECT_EQ(observer.at("consumer_variation_ns"), 125'000);
    EXPECT_EQ(observer.at("consumer_safety_interval_ns"), 22'000'000);
    EXPECT_EQ(observer.at("consumer_interval_ns"), 22'000'000);
    observed = response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Patch,
        R"({"category":"event","type":"gallery_patch","target":"scene","patch":{"feedback":{"enabled":false},"frequency_limit_enabled":false}})")));
    observer = observed.at("telemetry").at("kernel_observer");
    EXPECT_FALSE(observer.at("frequency_limit_enabled").get<bool>());
    EXPECT_FALSE(observer.at("consumer_feedback_enabled").get<bool>());
    EXPECT_EQ(observer.at("target_interval_ns"), 0);
    EXPECT_EQ(observer.at("consumer_interval_ns"), 0);
    EXPECT_TRUE(observer.at("limit_state") == "paint_limited" ||
                observer.at("limit_state") == "render_limited" ||
                observer.at("limit_state") == "unlimited");
    const auto rejected = response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Patch,
        R"({"category":"event","type":"gallery_patch","target":"scene","patch":{"pixel_stream_fps":30}})")));
    EXPECT_EQ(rejected.at("type"), "error");
}

TEST(RenderiveWebGallery, ConsumerFeedbackSourcesCanBeSelectedIndependentlyAndManually) {
    Gallery_Plot_Session session(true);
    ASSERT_EQ(response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Open, open_message("spectrum", "low_latency")))).at("type"),
              "case_state");
    ASSERT_EQ(patch_controls(session, {
        {"frequency_limit_enabled", false},
        {"feedback", {{"enabled", true}, {"pixel", true},
                      {"presentation", false}, {"manual", false}}}
    }).at("type"), "case_state");
    auto observed = response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Observe,
        R"({"category":"event","type":"gallery_observe","client_metrics":{"transport_fps":25,"presentation_fps":60,"websocket_buffered_bytes":0,"changed_pixel_frames":1,"duplicate_pixel_frames":0,"frame_request_timeout_count":0,"frame_round_trip_ms":5,"frame_round_trip_average_ms":4.8,"frame_round_trip_deviation_ms":0.2,"frame_round_trip_p95_ms":5.1,"frame_round_trip_p99_ms":5.3,"display_interval_ms":16,"display_interval_average_ms":16.2,"display_interval_latest_ms":16,"display_interval_p95_ms":17,"display_interval_p99_ms":18,"display_interval_deviation_ms":0.5,"overwritten_pixel_frames":0,"last_pixel_receive_age_ms":1,"last_pixel_change_age_ms":1}})")));
    auto observer = observed.at("telemetry").at("kernel_observer");
    auto feedback = observed.at("telemetry").at("consumer_feedback");
    EXPECT_EQ(feedback.at("source"), "pixel");
    EXPECT_EQ(feedback.at("pixel_interval_ns"), 40'000'000);
    EXPECT_EQ(feedback.at("presentation_interval_ns"), 16'000'000);
    EXPECT_EQ(observer.at("consumer_sample_interval_ns"), 40'000'000);
    observed = patch_controls(session, {
        {"feedback", {{"pixel", false}, {"presentation", true}}}
    });
    observer = observed.at("telemetry").at("kernel_observer");
    feedback = observed.at("telemetry").at("consumer_feedback");
    EXPECT_EQ(feedback.at("source"), "presentation");
    EXPECT_EQ(observer.at("consumer_sample_interval_ns"), 16'000'000);
    observed = patch_controls(session, {
        {"feedback", {{"presentation", false}, {"manual", true},
                      {"manual_fps", 20.0}}}
    });
    observer = observed.at("telemetry").at("kernel_observer");
    feedback = observed.at("telemetry").at("consumer_feedback");
    EXPECT_EQ(feedback.at("source"), "manual");
    EXPECT_EQ(feedback.at("manual_interval_ns"), 50'000'000);
    EXPECT_EQ(observer.at("consumer_sample_interval_ns"), 50'000'000);
    observed = patch_controls(session, {
        {"feedback", {{"manual", false}}}
    });
    observer = observed.at("telemetry").at("kernel_observer");
    feedback = observed.at("telemetry").at("consumer_feedback");
    EXPECT_EQ(feedback.at("source"), "none");
    EXPECT_FALSE(observer.at("consumer_feedback_enabled").get<bool>());
    EXPECT_TRUE(feedback.at("master_enabled").get<bool>());
    EXPECT_EQ(observer.at("consumer_interval_ns"), 0);
}

TEST(RenderiveWebGallery, PlaybackStrategyQueuesWithoutAutomaticConsumptionAndReportsEmptyQueue) {
    Gallery_Plot_Session session(true);
    const auto opened = response_json(session.handle(gallery_request(
        Gallery_Request_Kind::Open, open_message("spectrum", "playback"))));
    ASSERT_EQ(opened.at("type"), "case_state");
    const auto baseline = opened.at("telemetry").at("kernel_observer");
    EXPECT_EQ(baseline.at("mode"), "playback");
    EXPECT_EQ(baseline.at("limit_state"), "not_applicable");

    std::this_thread::sleep_for(std::chrono::milliseconds(80));
    auto observer = observe_telemetry(session).at("kernel_observer");
    EXPECT_EQ(observer.at("latest_sequence"), baseline.at("latest_sequence"));
    EXPECT_EQ(observer.at("consumed_frame_count"), baseline.at("consumed_frame_count"));

    auto state = invoke_action(session, "mode_enqueue_burst", 5);
    observer = state.at("telemetry").at("kernel_observer");
    EXPECT_EQ(observer.at("last_event"), "enqueued");
    EXPECT_EQ(observer.at("pending_frame_count"), 5);
    std::this_thread::sleep_for(std::chrono::milliseconds(20));
    EXPECT_EQ(observe_telemetry(session).at("kernel_observer").at("pending_frame_count"), 5);

    state = invoke_action(session, "mode_dequeue");
    observer = state.at("telemetry").at("kernel_observer");
    EXPECT_EQ(observer.at("last_event"), "rendered");
    EXPECT_EQ(observer.at("pending_frame_count"), 4);
    EXPECT_GT(observer.at("queue_wait_ns").get<std::uint64_t>(), 0U);

    state = invoke_action(session, "mode_cycle");
    observer = state.at("telemetry").at("kernel_observer");
    EXPECT_EQ(observer.at("last_event"), "rendered");
    EXPECT_EQ(observer.at("pending_frame_count"), 4);

    for (int remaining = 3; remaining >= 0; --remaining) {
        state = invoke_action(session, "mode_dequeue");
        observer = state.at("telemetry").at("kernel_observer");
        EXPECT_EQ(observer.at("pending_frame_count"), remaining);
    }
    const auto failed_before = observer.at("failed_operation_count").get<std::uint64_t>();
    state = invoke_action(session, "mode_dequeue");
    observer = state.at("telemetry").at("kernel_observer");
    EXPECT_EQ(observer.at("last_event"), "queue_empty");
    EXPECT_EQ(observer.at("failed_operation_count").get<std::uint64_t>(), failed_before + 1);
    EXPECT_EQ(observer.at("pending_frame_count"), 0);

    const auto sequence_after_actions = observer.at("latest_sequence");
    std::this_thread::sleep_for(std::chrono::milliseconds(80));
    EXPECT_EQ(observe_telemetry(session).at("kernel_observer").at("latest_sequence"),
              sequence_after_actions);
}

TEST(RenderiveWebGallery, BackendRejectsActionsHiddenByCurrentCaseOrFrameMode) {
    struct Rejected_Action {
        std::string_view mode;
        std::string_view action;
    };
    constexpr std::array rejected{
        Rejected_Action{"manual", "mode_enqueue"},
        Rejected_Action{"low_latency", "mode_refresh"},
        Rejected_Action{"playback", "mode_discard"},
        Rejected_Action{"manual", "append_row"}
    };
    for (const auto& item : rejected) {
        Gallery_Plot_Session session;
        ASSERT_EQ(response_json(session.handle(gallery_request(
            Gallery_Request_Kind::Open, open_message("spectrum", item.mode)))).at("type"),
                  "case_state") << item.mode;
        const auto response = invoke_action(session, item.action);
        EXPECT_EQ(response.at("type"), "error") << item.mode << '/' << item.action;
        EXPECT_TRUE(response.at("field_errors").contains("action"))
            << item.mode << '/' << item.action;
    }
}

TEST(RenderiveWebGallery, ProductionLowLatencySessionRendersWithoutPixelPulls) {
    Gallery_Plot_Session session(true);
    ASSERT_TRUE(session.handle(gallery_request(
        Gallery_Request_Kind::Open, open_message("spectrum", "low_latency"))));
    ASSERT_TRUE(session.handle(gallery_request(
        Gallery_Request_Kind::Patch,
        R"({"category":"event","type":"gallery_patch","target":"scene","patch":{"max_render_fps":120}})")));
    ASSERT_TRUE(wait_for_condition([&session] {
        return successful_render_count(session) >= 4;
    }));
    const auto observed = session.handle(gallery_request(
        Gallery_Request_Kind::Observe,
        R"({"category":"event","type":"gallery_observe","client_metrics":{"transport_fps":120,"presentation_fps":60,"websocket_buffered_bytes":0,"changed_pixel_frames":17,"duplicate_pixel_frames":3,"frame_request_timeout_count":2,"frame_round_trip_ms":4.5,"frame_round_trip_average_ms":4.2,"frame_round_trip_deviation_ms":0.3,"frame_round_trip_p95_ms":5.1,"frame_round_trip_p99_ms":5.4,"display_interval_ms":16.7,"display_interval_average_ms":16.8,"display_interval_latest_ms":16.9,"display_interval_p95_ms":17.4,"display_interval_p99_ms":18.2,"display_interval_deviation_ms":0.7,"overwritten_pixel_frames":5,"last_pixel_receive_age_ms":8.5,"last_pixel_change_age_ms":12.5}})"));
    ASSERT_TRUE(observed.has_value());
    const auto telemetry = parse_json(observed->payload).at("telemetry");
    const auto& performance = telemetry.at("performance");
    EXPECT_TRUE(performance.at("automatic_low_latency_scheduler").get<bool>());
    EXPECT_GE(performance.at("successful_render_count").get<std::uint64_t>(), 4U);
    EXPECT_GT(performance.at("measured_fps").get<double>(), 0.0);
    EXPECT_DOUBLE_EQ(telemetry.at("kernel_observer").at("frequency_hz"), 120.0);
    EXPECT_EQ(telemetry.at("kernel_observer").at("target_interval_ns"), 8'333'333);
    EXPECT_TRUE(telemetry.at("kernel_observer").at("limit_state") == "frequency_limited" ||
                telemetry.at("kernel_observer").at("limit_state") == "paint_limited" ||
                telemetry.at("kernel_observer").at("limit_state") == "render_limited" ||
                telemetry.at("kernel_observer").at("limit_state") == "consumer_limited");
    EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("transport_fps"), 120.0);
    EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("presentation_fps"), 60.0);
    EXPECT_EQ(telemetry.at("client_performance").at("changed_pixel_frames"), 17);
    EXPECT_EQ(telemetry.at("client_performance").at("duplicate_pixel_frames"), 3);
    EXPECT_EQ(telemetry.at("client_performance").at("frame_request_timeout_count"), 2);
    EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("frame_round_trip_ms"), 4.5);
    EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("frame_round_trip_average_ms"), 4.2);
    EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("frame_round_trip_deviation_ms"), 0.3);
    EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("frame_round_trip_p95_ms"), 5.1);
    EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("frame_round_trip_p99_ms"), 5.4);
    EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("display_interval_ms"), 16.7);
    EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("display_interval_average_ms"), 16.8);
    EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("display_interval_latest_ms"), 16.9);
    EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("display_interval_p95_ms"), 17.4);
    EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("display_interval_p99_ms"), 18.2);
    EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("display_interval_deviation_ms"), 0.7);
    EXPECT_EQ(telemetry.at("client_performance").at("overwritten_pixel_frames"), 5);
    EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("last_pixel_receive_age_ms"), 8.5);
    EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("last_pixel_change_age_ms"), 12.5);
}

TEST(RenderiveWebGallery, ProductionLowLatencyStopsWhileHiddenAndResumes) {
    Gallery_Plot_Session session(true);
    ASSERT_TRUE(session.handle(gallery_request(
        Gallery_Request_Kind::Open, open_message("spectrum", "low_latency"))));
    ASSERT_TRUE(session.handle(gallery_request(
        Gallery_Request_Kind::Patch,
        R"({"category":"event","type":"gallery_patch","target":"scene","patch":{"max_render_fps":120}})")));
    ASSERT_TRUE(wait_for_condition([&session] {
        return successful_render_count(session) >= 3;
    }));
    set_gallery_view_active(session, false);
    const auto stopped_count = successful_render_count(session);
    EXPECT_FALSE(session.handle(Frame_Request{}).has_value());
    std::this_thread::sleep_for(std::chrono::milliseconds(80));
    EXPECT_EQ(successful_render_count(session), stopped_count);
    set_gallery_view_active(session, true);
    EXPECT_TRUE(wait_for_condition([&session, stopped_count] {
        return successful_render_count(session) > stopped_count;
    }));
    EXPECT_TRUE(session.handle(Frame_Request{}).has_value());
}

TEST(RenderiveWebGallery, ProductionLowLatencyOnlyRendersActiveSessions) {
    constexpr std::size_t session_count = 8;
    std::vector<std::unique_ptr<Gallery_Plot_Session>> sessions;
    sessions.reserve(session_count);
    for (std::size_t index = 0; index < session_count; ++index) {
        auto session = std::make_unique<Gallery_Plot_Session>(true);
        ASSERT_TRUE(session->handle(gallery_request(
            Gallery_Request_Kind::Open, open_message("spectrum", "low_latency"))));
        ASSERT_TRUE(session->handle(gallery_request(
            Gallery_Request_Kind::Patch,
            R"({"category":"event","type":"gallery_patch","target":"scene","patch":{"max_render_fps":60}})")));
        if (index >= 2)
            set_gallery_view_active(*session, false);
        sessions.push_back(std::move(session));
    }
    for (std::size_t index = 0; index < 2; ++index) {
        ASSERT_TRUE(wait_for_condition([&sessions, index] {
            return successful_render_count(*sessions[index]) >= 2;
        }));
    }
    std::array<std::uint64_t, session_count> hidden_counts{};
    for (std::size_t index = 2; index < session_count; ++index)
        hidden_counts[index] = successful_render_count(*sessions[index]);
    std::this_thread::sleep_for(std::chrono::milliseconds(80));
    for (std::size_t index = 2; index < session_count; ++index)
        EXPECT_EQ(successful_render_count(*sessions[index]), hidden_counts[index]) << index;
    set_gallery_view_active(*sessions[0], false);
    set_gallery_view_active(*sessions[1], false);
    const auto first_stopped = successful_render_count(*sessions[0]);
    const auto second_stopped = successful_render_count(*sessions[1]);
    set_gallery_view_active(*sessions[2], true);
    set_gallery_view_active(*sessions[3], true);
    EXPECT_TRUE(wait_for_condition([&sessions, baseline = hidden_counts[2]] {
        return successful_render_count(*sessions[2]) > baseline;
    }));
    EXPECT_TRUE(wait_for_condition([&sessions, baseline = hidden_counts[3]] {
        return successful_render_count(*sessions[3]) > baseline;
    }));
    std::this_thread::sleep_for(std::chrono::milliseconds(80));
    EXPECT_EQ(successful_render_count(*sessions[0]), first_stopped);
    EXPECT_EQ(successful_render_count(*sessions[1]), second_stopped);
}

void expect_low_latency_canvas_to_change(std::string_view case_id) {
    SCOPED_TRACE(std::string(case_id));
    Gallery_Plot_Session session(true);
    ASSERT_TRUE(session.handle(gallery_request(
        Gallery_Request_Kind::Open, open_message(case_id, "low_latency"))));
    ASSERT_FALSE(session.handle(Viewport_Resize{{240, 180}}).has_value());
    ASSERT_TRUE(session.handle(gallery_request(
        Gallery_Request_Kind::Patch,
        R"({"category":"event","type":"gallery_patch","target":"scene","patch":{"max_render_fps":120}})")));

    std::this_thread::sleep_for(std::chrono::milliseconds(90));
    const auto first = session.handle(Frame_Request{});
    ASSERT_TRUE(first.has_value());
    ASSERT_EQ(first->type, Web_Response_Type::Pixels);
    std::this_thread::sleep_for(std::chrono::milliseconds(90));
    const auto second = session.handle(Frame_Request{});
    ASSERT_TRUE(second.has_value());
    ASSERT_EQ(second->type, Web_Response_Type::Pixels);
    EXPECT_NE(first->payload, second->payload);

    const auto observed = session.handle(gallery_request(
        Gallery_Request_Kind::Observe,
        R"({"category":"event","type":"gallery_observe"})"));
    ASSERT_TRUE(observed.has_value());
    const auto telemetry = parse_json(observed->payload).at("telemetry");
    EXPECT_GT(telemetry.at("frame_index").get<std::uint64_t>(), 2U);
    EXPECT_GT(telemetry.at("kernel_observer").at("latest_sequence").get<std::uint64_t>(), 1U);
}

TEST(RenderiveWebGallery, AxisLabLowLatencyPixelsChange) {
    expect_low_latency_canvas_to_change("axis_lab");
}

TEST(RenderiveWebGallery, SpectrumLowLatencyPixelsChange) {
    expect_low_latency_canvas_to_change("spectrum");
}

TEST(RenderiveWebGallery, AfterglowLowLatencyPixelsChange) {
    expect_low_latency_canvas_to_change("afterglow");
}

TEST(RenderiveWebGallery, SweepSpectrumLowLatencyPixelsChange) {
    expect_low_latency_canvas_to_change("sweep_spectrum");
}

TEST(RenderiveWebGallery, WaterfallLowLatencyPixelsChange) {
    expect_low_latency_canvas_to_change("waterfall");
}

TEST(RenderiveWebGallery, FrequencyTraceLowLatencyPixelsChange) {
    expect_low_latency_canvas_to_change("frequency_trace");
}

TEST(RenderiveWebGallery, SelectionOverlayLowLatencyPixelsChange) {
    expect_low_latency_canvas_to_change("selection_overlay");
}

TEST(RenderiveWebGallery, ConstellationLowLatencyPixelsChange) {
    expect_low_latency_canvas_to_change("constellation");
}

TEST(RenderiveWebGallery, DirectPropertyChangesAndAllImageModesRender) {
    Gallery_Plot_Session constellation;
    ASSERT_TRUE(constellation.handle(gallery_request(
        Gallery_Request_Kind::Open,
        open_message("constellation", "low_latency"))));
    const auto patched = constellation.handle(gallery_request(
        Gallery_Request_Kind::Patch,
        R"({"category":"event","type":"gallery_patch","target":"constellation","patch":{"type":"Psk16","phase_offset_radians":0.75}})"));
    ASSERT_TRUE(patched.has_value());
    const auto patched_json = parse_json(patched->payload);
    EXPECT_EQ(patched_json.at("controls").at("resources").at(3).at("data").at("type"),
              "Psk16");
    ASSERT_TRUE(constellation.handle(Frame_Request{}).has_value());

    Gallery_Plot_Session waterfall;
    ASSERT_TRUE(waterfall.handle(gallery_request(
        Gallery_Request_Kind::Open,
        open_message("waterfall", "low_latency"))));
    for (const std::string_view mode : {"Nearest", "Bilinear", "Bicubic"}) {
        const std::string patch =
            std::string(R"({"category":"event","type":"gallery_patch","target":"waterfall","patch":{"interpolation_mode":")") +
            std::string(mode) + R"("}})";
        ASSERT_TRUE(waterfall.handle(gallery_request(Gallery_Request_Kind::Patch, patch)).has_value())
            << mode;
        ASSERT_TRUE(waterfall.handle(Frame_Request{}).has_value()) << mode;
    }
    const auto palette = waterfall.handle(gallery_request(
        Gallery_Request_Kind::Patch,
        R"({"category":"event","type":"gallery_patch","target":"waterfall","patch":{"color_map":"Ember"}})"));
    ASSERT_TRUE(palette.has_value());
    ASSERT_TRUE(waterfall.handle(Frame_Request{}).has_value());
    const auto waterfall_observer = waterfall.handle(gallery_request(
        Gallery_Request_Kind::Observe,
        R"({"category":"event","type":"gallery_observe"})"));
    ASSERT_TRUE(waterfall_observer.has_value());
    const auto waterfall_telemetry = parse_json(waterfall_observer->payload).at("telemetry");
    EXPECT_GT(waterfall_telemetry.at("waterfall").at("row_count").get<std::size_t>(), 0U);
    EXPECT_GT(waterfall_telemetry.at("waterfall").at("stored_point_count").get<std::size_t>(), 0U);
    EXPECT_GT(waterfall_telemetry.at("waterfall").at("rendered_cell_count").get<std::size_t>(), 0U);
}

} // namespace
} // namespace renderive::web
