// WARNING: This file is automatically generated. Any changes will be lost.
#include "twitch-eventsub-ws/payloads/channel-moderate-v2.hpp"

#include "twitch-eventsub-ws/chrono.hpp"  // IWYU pragma: keep
#include "twitch-eventsub-ws/detail/errors.hpp"
#include "twitch-eventsub-ws/detail/variant.hpp"  // IWYU pragma: keep

#include <boost/json.hpp>

namespace chatterino::eventsub::lib::payload::channel_moderate::v2 {

boost::json::result_for<Followers, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<Followers> /* tag */,
    const boost::json::value &jvRoot)
{
    if (!jvRoot.is_object())
    {
        EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
    }
    const auto &root = jvRoot.get_object();

    static_assert(
        std::is_trivially_copyable_v<std::remove_reference_t<
            decltype(std::declval<Followers>().followDurationMinutes)>>);
    const auto *jvfollowDurationMinutes =
        root.if_contains("follow_duration_minutes");
    if (jvfollowDurationMinutes == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto followDurationMinutes =
        boost::json::try_value_to<int>(*jvfollowDurationMinutes);

    if (followDurationMinutes.has_error())
    {
        return followDurationMinutes.error();
    }

    return Followers{
        .followDurationMinutes = followDurationMinutes.value(),
    };
}

boost::json::result_for<FollowersOff, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<FollowersOff> /* tag */,
    const boost::json::value & /* jvRoot */)
{
    return FollowersOff{};
}

boost::json::result_for<Slow, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<Slow> /* tag */,
    const boost::json::value &jvRoot)
{
    if (!jvRoot.is_object())
    {
        EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
    }
    const auto &root = jvRoot.get_object();

    static_assert(std::is_trivially_copyable_v<std::remove_reference_t<
                      decltype(std::declval<Slow>().waitTimeSeconds)>>);
    const auto *jvwaitTimeSeconds = root.if_contains("wait_time_seconds");
    if (jvwaitTimeSeconds == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto waitTimeSeconds = boost::json::try_value_to<int>(*jvwaitTimeSeconds);

    if (waitTimeSeconds.has_error())
    {
        return waitTimeSeconds.error();
    }

    return Slow{
        .waitTimeSeconds = waitTimeSeconds.value(),
    };
}

boost::json::result_for<SlowOff, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<SlowOff> /* tag */,
    const boost::json::value & /* jvRoot */)
{
    return SlowOff{};
}

boost::json::result_for<Vip, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<Vip> /* tag */,
    const boost::json::value &jvRoot)
{
    if (!jvRoot.is_object())
    {
        EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
    }
    const auto &root = jvRoot.get_object();

    const auto *jvuserID = root.if_contains("user_id");
    if (jvuserID == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userID = boost::json::try_value_to<String>(*jvuserID);

    if (userID.has_error())
    {
        return userID.error();
    }

    const auto *jvuserLogin = root.if_contains("user_login");
    if (jvuserLogin == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userLogin = boost::json::try_value_to<String>(*jvuserLogin);

    if (userLogin.has_error())
    {
        return userLogin.error();
    }

    const auto *jvuserName = root.if_contains("user_name");
    if (jvuserName == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userName = boost::json::try_value_to<String>(*jvuserName);

    if (userName.has_error())
    {
        return userName.error();
    }

    return Vip{
        .userID = std::move(userID.value()),
        .userLogin = std::move(userLogin.value()),
        .userName = std::move(userName.value()),
    };
}

boost::json::result_for<Unvip, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<Unvip> /* tag */,
    const boost::json::value &jvRoot)
{
    if (!jvRoot.is_object())
    {
        EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
    }
    const auto &root = jvRoot.get_object();

    const auto *jvuserID = root.if_contains("user_id");
    if (jvuserID == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userID = boost::json::try_value_to<String>(*jvuserID);

    if (userID.has_error())
    {
        return userID.error();
    }

    const auto *jvuserLogin = root.if_contains("user_login");
    if (jvuserLogin == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userLogin = boost::json::try_value_to<String>(*jvuserLogin);

    if (userLogin.has_error())
    {
        return userLogin.error();
    }

    const auto *jvuserName = root.if_contains("user_name");
    if (jvuserName == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userName = boost::json::try_value_to<String>(*jvuserName);

    if (userName.has_error())
    {
        return userName.error();
    }

    return Unvip{
        .userID = std::move(userID.value()),
        .userLogin = std::move(userLogin.value()),
        .userName = std::move(userName.value()),
    };
}

boost::json::result_for<Mod, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<Mod> /* tag */,
    const boost::json::value &jvRoot)
{
    if (!jvRoot.is_object())
    {
        EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
    }
    const auto &root = jvRoot.get_object();

    const auto *jvuserID = root.if_contains("user_id");
    if (jvuserID == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userID = boost::json::try_value_to<String>(*jvuserID);

    if (userID.has_error())
    {
        return userID.error();
    }

    const auto *jvuserLogin = root.if_contains("user_login");
    if (jvuserLogin == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userLogin = boost::json::try_value_to<String>(*jvuserLogin);

    if (userLogin.has_error())
    {
        return userLogin.error();
    }

    const auto *jvuserName = root.if_contains("user_name");
    if (jvuserName == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userName = boost::json::try_value_to<String>(*jvuserName);

    if (userName.has_error())
    {
        return userName.error();
    }

    return Mod{
        .userID = std::move(userID.value()),
        .userLogin = std::move(userLogin.value()),
        .userName = std::move(userName.value()),
    };
}

boost::json::result_for<Unmod, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<Unmod> /* tag */,
    const boost::json::value &jvRoot)
{
    if (!jvRoot.is_object())
    {
        EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
    }
    const auto &root = jvRoot.get_object();

    const auto *jvuserID = root.if_contains("user_id");
    if (jvuserID == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userID = boost::json::try_value_to<String>(*jvuserID);

    if (userID.has_error())
    {
        return userID.error();
    }

    const auto *jvuserLogin = root.if_contains("user_login");
    if (jvuserLogin == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userLogin = boost::json::try_value_to<String>(*jvuserLogin);

    if (userLogin.has_error())
    {
        return userLogin.error();
    }

    const auto *jvuserName = root.if_contains("user_name");
    if (jvuserName == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userName = boost::json::try_value_to<String>(*jvuserName);

    if (userName.has_error())
    {
        return userName.error();
    }

    return Unmod{
        .userID = std::move(userID.value()),
        .userLogin = std::move(userLogin.value()),
        .userName = std::move(userName.value()),
    };
}

boost::json::result_for<Ban, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<Ban> /* tag */,
    const boost::json::value &jvRoot)
{
    if (!jvRoot.is_object())
    {
        EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
    }
    const auto &root = jvRoot.get_object();

    const auto *jvuserID = root.if_contains("user_id");
    if (jvuserID == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userID = boost::json::try_value_to<String>(*jvuserID);

    if (userID.has_error())
    {
        return userID.error();
    }

    const auto *jvuserLogin = root.if_contains("user_login");
    if (jvuserLogin == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userLogin = boost::json::try_value_to<String>(*jvuserLogin);

    if (userLogin.has_error())
    {
        return userLogin.error();
    }

    const auto *jvuserName = root.if_contains("user_name");
    if (jvuserName == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userName = boost::json::try_value_to<String>(*jvuserName);

    if (userName.has_error())
    {
        return userName.error();
    }

    const auto *jvreason = root.if_contains("reason");
    if (jvreason == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto reason = boost::json::try_value_to<String>(*jvreason);

    if (reason.has_error())
    {
        return reason.error();
    }

    return Ban{
        .userID = std::move(userID.value()),
        .userLogin = std::move(userLogin.value()),
        .userName = std::move(userName.value()),
        .reason = std::move(reason.value()),
    };
}

boost::json::result_for<SharedChatBan, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<SharedChatBan> /* tag */,
    const boost::json::value &jvRoot)
{
    auto base = boost::json::try_value_to<Ban>(jvRoot);
    if (base.has_error())
    {
        return base.error();
    }

    return SharedChatBan{std::move(*base)};
}

boost::json::result_for<Unban, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<Unban> /* tag */,
    const boost::json::value &jvRoot)
{
    if (!jvRoot.is_object())
    {
        EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
    }
    const auto &root = jvRoot.get_object();

    const auto *jvuserID = root.if_contains("user_id");
    if (jvuserID == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userID = boost::json::try_value_to<String>(*jvuserID);

    if (userID.has_error())
    {
        return userID.error();
    }

    const auto *jvuserLogin = root.if_contains("user_login");
    if (jvuserLogin == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userLogin = boost::json::try_value_to<String>(*jvuserLogin);

    if (userLogin.has_error())
    {
        return userLogin.error();
    }

    const auto *jvuserName = root.if_contains("user_name");
    if (jvuserName == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userName = boost::json::try_value_to<String>(*jvuserName);

    if (userName.has_error())
    {
        return userName.error();
    }

    return Unban{
        .userID = std::move(userID.value()),
        .userLogin = std::move(userLogin.value()),
        .userName = std::move(userName.value()),
    };
}

boost::json::result_for<SharedChatUnban, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<SharedChatUnban> /* tag */,
    const boost::json::value &jvRoot)
{
    auto base = boost::json::try_value_to<Unban>(jvRoot);
    if (base.has_error())
    {
        return base.error();
    }

    return SharedChatUnban{std::move(*base)};
}

boost::json::result_for<Timeout, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<Timeout> /* tag */,
    const boost::json::value &jvRoot)
{
    if (!jvRoot.is_object())
    {
        EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
    }
    const auto &root = jvRoot.get_object();

    const auto *jvuserID = root.if_contains("user_id");
    if (jvuserID == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userID = boost::json::try_value_to<String>(*jvuserID);

    if (userID.has_error())
    {
        return userID.error();
    }

    const auto *jvuserLogin = root.if_contains("user_login");
    if (jvuserLogin == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userLogin = boost::json::try_value_to<String>(*jvuserLogin);

    if (userLogin.has_error())
    {
        return userLogin.error();
    }

    const auto *jvuserName = root.if_contains("user_name");
    if (jvuserName == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userName = boost::json::try_value_to<String>(*jvuserName);

    if (userName.has_error())
    {
        return userName.error();
    }

    const auto *jvreason = root.if_contains("reason");
    if (jvreason == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto reason = boost::json::try_value_to<String>(*jvreason);

    if (reason.has_error())
    {
        return reason.error();
    }

    static_assert(std::is_trivially_copyable_v<std::remove_reference_t<
                      decltype(std::declval<Timeout>().expiresAt)>>);
    const auto *jvexpiresAt = root.if_contains("expires_at");
    if (jvexpiresAt == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto expiresAt =
        boost::json::try_value_to<std::chrono::system_clock::time_point>(
            *jvexpiresAt, AsISO8601());

    if (expiresAt.has_error())
    {
        return expiresAt.error();
    }

    return Timeout{
        .userID = std::move(userID.value()),
        .userLogin = std::move(userLogin.value()),
        .userName = std::move(userName.value()),
        .reason = std::move(reason.value()),
        .expiresAt = expiresAt.value(),
    };
}

boost::json::result_for<SharedChatTimeout, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<SharedChatTimeout> /* tag */,
    const boost::json::value &jvRoot)
{
    auto base = boost::json::try_value_to<Timeout>(jvRoot);
    if (base.has_error())
    {
        return base.error();
    }

    return SharedChatTimeout{std::move(*base)};
}

boost::json::result_for<Untimeout, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<Untimeout> /* tag */,
    const boost::json::value &jvRoot)
{
    if (!jvRoot.is_object())
    {
        EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
    }
    const auto &root = jvRoot.get_object();

    const auto *jvuserID = root.if_contains("user_id");
    if (jvuserID == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userID = boost::json::try_value_to<String>(*jvuserID);

    if (userID.has_error())
    {
        return userID.error();
    }

    const auto *jvuserLogin = root.if_contains("user_login");
    if (jvuserLogin == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userLogin = boost::json::try_value_to<String>(*jvuserLogin);

    if (userLogin.has_error())
    {
        return userLogin.error();
    }

    const auto *jvuserName = root.if_contains("user_name");
    if (jvuserName == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userName = boost::json::try_value_to<String>(*jvuserName);

    if (userName.has_error())
    {
        return userName.error();
    }

    return Untimeout{
        .userID = std::move(userID.value()),
        .userLogin = std::move(userLogin.value()),
        .userName = std::move(userName.value()),
    };
}

boost::json::result_for<SharedChatUntimeout, boost::json::value>::type
    tag_invoke(boost::json::try_value_to_tag<SharedChatUntimeout> /* tag */,
               const boost::json::value &jvRoot)
{
    auto base = boost::json::try_value_to<Untimeout>(jvRoot);
    if (base.has_error())
    {
        return base.error();
    }

    return SharedChatUntimeout{std::move(*base)};
}

boost::json::result_for<Raid, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<Raid> /* tag */,
    const boost::json::value &jvRoot)
{
    if (!jvRoot.is_object())
    {
        EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
    }
    const auto &root = jvRoot.get_object();

    const auto *jvuserID = root.if_contains("user_id");
    if (jvuserID == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userID = boost::json::try_value_to<String>(*jvuserID);

    if (userID.has_error())
    {
        return userID.error();
    }

    const auto *jvuserLogin = root.if_contains("user_login");
    if (jvuserLogin == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userLogin = boost::json::try_value_to<String>(*jvuserLogin);

    if (userLogin.has_error())
    {
        return userLogin.error();
    }

    const auto *jvuserName = root.if_contains("user_name");
    if (jvuserName == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userName = boost::json::try_value_to<String>(*jvuserName);

    if (userName.has_error())
    {
        return userName.error();
    }

    static_assert(std::is_trivially_copyable_v<std::remove_reference_t<
                      decltype(std::declval<Raid>().viewerCount)>>);
    const auto *jvviewerCount = root.if_contains("viewer_count");
    if (jvviewerCount == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto viewerCount = boost::json::try_value_to<int>(*jvviewerCount);

    if (viewerCount.has_error())
    {
        return viewerCount.error();
    }

    return Raid{
        .userID = std::move(userID.value()),
        .userLogin = std::move(userLogin.value()),
        .userName = std::move(userName.value()),
        .viewerCount = viewerCount.value(),
    };
}

boost::json::result_for<Unraid, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<Unraid> /* tag */,
    const boost::json::value &jvRoot)
{
    if (!jvRoot.is_object())
    {
        EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
    }
    const auto &root = jvRoot.get_object();

    const auto *jvuserID = root.if_contains("user_id");
    if (jvuserID == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userID = boost::json::try_value_to<String>(*jvuserID);

    if (userID.has_error())
    {
        return userID.error();
    }

    const auto *jvuserLogin = root.if_contains("user_login");
    if (jvuserLogin == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userLogin = boost::json::try_value_to<String>(*jvuserLogin);

    if (userLogin.has_error())
    {
        return userLogin.error();
    }

    const auto *jvuserName = root.if_contains("user_name");
    if (jvuserName == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userName = boost::json::try_value_to<String>(*jvuserName);

    if (userName.has_error())
    {
        return userName.error();
    }

    return Unraid{
        .userID = std::move(userID.value()),
        .userLogin = std::move(userLogin.value()),
        .userName = std::move(userName.value()),
    };
}

boost::json::result_for<Delete, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<Delete> /* tag */,
    const boost::json::value &jvRoot)
{
    if (!jvRoot.is_object())
    {
        EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
    }
    const auto &root = jvRoot.get_object();

    const auto *jvuserID = root.if_contains("user_id");
    if (jvuserID == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userID = boost::json::try_value_to<String>(*jvuserID);

    if (userID.has_error())
    {
        return userID.error();
    }

    const auto *jvuserLogin = root.if_contains("user_login");
    if (jvuserLogin == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userLogin = boost::json::try_value_to<String>(*jvuserLogin);

    if (userLogin.has_error())
    {
        return userLogin.error();
    }

    const auto *jvuserName = root.if_contains("user_name");
    if (jvuserName == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userName = boost::json::try_value_to<String>(*jvuserName);

    if (userName.has_error())
    {
        return userName.error();
    }

    const auto *jvmessageID = root.if_contains("message_id");
    if (jvmessageID == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto messageID = boost::json::try_value_to<String>(*jvmessageID);

    if (messageID.has_error())
    {
        return messageID.error();
    }

    const auto *jvmessageBody = root.if_contains("message_body");
    if (jvmessageBody == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto messageBody = boost::json::try_value_to<String>(*jvmessageBody);

    if (messageBody.has_error())
    {
        return messageBody.error();
    }

    return Delete{
        .userID = std::move(userID.value()),
        .userLogin = std::move(userLogin.value()),
        .userName = std::move(userName.value()),
        .messageID = std::move(messageID.value()),
        .messageBody = std::move(messageBody.value()),
    };
}

boost::json::result_for<SharedChatDelete, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<SharedChatDelete> /* tag */,
    const boost::json::value &jvRoot)
{
    auto base = boost::json::try_value_to<Delete>(jvRoot);
    if (base.has_error())
    {
        return base.error();
    }

    return SharedChatDelete{std::move(*base)};
}

boost::json::result_for<AutomodTerms, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<AutomodTerms> /* tag */,
    const boost::json::value &jvRoot)
{
    if (!jvRoot.is_object())
    {
        EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
    }
    const auto &root = jvRoot.get_object();

    const auto *jvaction = root.if_contains("action");
    if (jvaction == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto action = boost::json::try_value_to<String>(*jvaction);

    if (action.has_error())
    {
        return action.error();
    }

    const auto *jvlist = root.if_contains("list");
    if (jvlist == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto list = boost::json::try_value_to<String>(*jvlist);

    if (list.has_error())
    {
        return list.error();
    }

    std::vector<chatterino::eventsub::lib::String> vterms;
    const auto *jvterms = root.if_contains("terms");
    if (jvterms != nullptr && !jvterms->is_null())
    {
        auto terms = boost::json::try_value_to<
            std::vector<chatterino::eventsub::lib::String>>(*jvterms);
        if (terms.has_error())
        {
            return terms.error();
        }
        else
        {
            vterms = std::move(terms.value());
        }
    }
    static_assert(std::is_trivially_copyable_v<std::remove_reference_t<
                      decltype(std::declval<AutomodTerms>().fromAutomod)>>);
    const auto *jvfromAutomod = root.if_contains("from_automod");
    if (jvfromAutomod == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto fromAutomod = boost::json::try_value_to<bool>(*jvfromAutomod);

    if (fromAutomod.has_error())
    {
        return fromAutomod.error();
    }

    return AutomodTerms{
        .action = std::move(action.value()),
        .list = std::move(list.value()),
        .terms = std::move(vterms),
        .fromAutomod = fromAutomod.value(),
    };
}

boost::json::result_for<AddBlockedTerm, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<AddBlockedTerm> /* tag */,
    const boost::json::value &jvRoot)
{
    auto base = boost::json::try_value_to<AutomodTerms>(jvRoot);
    if (base.has_error())
    {
        return base.error();
    }

    return AddBlockedTerm{std::move(*base)};
}

boost::json::result_for<AddPermittedTerm, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<AddPermittedTerm> /* tag */,
    const boost::json::value &jvRoot)
{
    auto base = boost::json::try_value_to<AutomodTerms>(jvRoot);
    if (base.has_error())
    {
        return base.error();
    }

    return AddPermittedTerm{std::move(*base)};
}

boost::json::result_for<RemoveBlockedTerm, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<RemoveBlockedTerm> /* tag */,
    const boost::json::value &jvRoot)
{
    auto base = boost::json::try_value_to<AutomodTerms>(jvRoot);
    if (base.has_error())
    {
        return base.error();
    }

    return RemoveBlockedTerm{std::move(*base)};
}

boost::json::result_for<RemovePermittedTerm, boost::json::value>::type
    tag_invoke(boost::json::try_value_to_tag<RemovePermittedTerm> /* tag */,
               const boost::json::value &jvRoot)
{
    auto base = boost::json::try_value_to<AutomodTerms>(jvRoot);
    if (base.has_error())
    {
        return base.error();
    }

    return RemovePermittedTerm{std::move(*base)};
}

boost::json::result_for<UnbanRequest, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<UnbanRequest> /* tag */,
    const boost::json::value &jvRoot)
{
    if (!jvRoot.is_object())
    {
        EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
    }
    const auto &root = jvRoot.get_object();

    static_assert(std::is_trivially_copyable_v<std::remove_reference_t<
                      decltype(std::declval<UnbanRequest>().isApproved)>>);
    const auto *jvisApproved = root.if_contains("is_approved");
    if (jvisApproved == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto isApproved = boost::json::try_value_to<bool>(*jvisApproved);

    if (isApproved.has_error())
    {
        return isApproved.error();
    }

    const auto *jvuserID = root.if_contains("user_id");
    if (jvuserID == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userID = boost::json::try_value_to<String>(*jvuserID);

    if (userID.has_error())
    {
        return userID.error();
    }

    const auto *jvuserLogin = root.if_contains("user_login");
    if (jvuserLogin == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userLogin = boost::json::try_value_to<String>(*jvuserLogin);

    if (userLogin.has_error())
    {
        return userLogin.error();
    }

    const auto *jvuserName = root.if_contains("user_name");
    if (jvuserName == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userName = boost::json::try_value_to<String>(*jvuserName);

    if (userName.has_error())
    {
        return userName.error();
    }

    const auto *jvmoderatorMessage = root.if_contains("moderator_message");
    if (jvmoderatorMessage == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto moderatorMessage =
        boost::json::try_value_to<String>(*jvmoderatorMessage);

    if (moderatorMessage.has_error())
    {
        return moderatorMessage.error();
    }

    return UnbanRequest{
        .isApproved = isApproved.value(),
        .userID = std::move(userID.value()),
        .userLogin = std::move(userLogin.value()),
        .userName = std::move(userName.value()),
        .moderatorMessage = std::move(moderatorMessage.value()),
    };
}

boost::json::result_for<ApproveUnbanRequest, boost::json::value>::type
    tag_invoke(boost::json::try_value_to_tag<ApproveUnbanRequest> /* tag */,
               const boost::json::value &jvRoot)
{
    auto base = boost::json::try_value_to<UnbanRequest>(jvRoot);
    if (base.has_error())
    {
        return base.error();
    }

    return ApproveUnbanRequest{std::move(*base)};
}

boost::json::result_for<DenyUnbanRequest, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<DenyUnbanRequest> /* tag */,
    const boost::json::value &jvRoot)
{
    auto base = boost::json::try_value_to<UnbanRequest>(jvRoot);
    if (base.has_error())
    {
        return base.error();
    }

    return DenyUnbanRequest{std::move(*base)};
}

boost::json::result_for<Warn, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<Warn> /* tag */,
    const boost::json::value &jvRoot)
{
    if (!jvRoot.is_object())
    {
        EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
    }
    const auto &root = jvRoot.get_object();

    const auto *jvuserID = root.if_contains("user_id");
    if (jvuserID == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userID = boost::json::try_value_to<String>(*jvuserID);

    if (userID.has_error())
    {
        return userID.error();
    }

    const auto *jvuserLogin = root.if_contains("user_login");
    if (jvuserLogin == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userLogin = boost::json::try_value_to<String>(*jvuserLogin);

    if (userLogin.has_error())
    {
        return userLogin.error();
    }

    const auto *jvuserName = root.if_contains("user_name");
    if (jvuserName == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto userName = boost::json::try_value_to<String>(*jvuserName);

    if (userName.has_error())
    {
        return userName.error();
    }

    const auto *jvreason = root.if_contains("reason");
    if (jvreason == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto reason = boost::json::try_value_to<String>(*jvreason);

    if (reason.has_error())
    {
        return reason.error();
    }

    std::vector<chatterino::eventsub::lib::String> vchatRulesCited;
    const auto *jvchatRulesCited = root.if_contains("chat_rules_cited");
    if (jvchatRulesCited != nullptr && !jvchatRulesCited->is_null())
    {
        auto chatRulesCited = boost::json::try_value_to<
            std::vector<chatterino::eventsub::lib::String>>(*jvchatRulesCited);
        if (chatRulesCited.has_error())
        {
            return chatRulesCited.error();
        }
        else
        {
            vchatRulesCited = std::move(chatRulesCited.value());
        }
    }

    return Warn{
        .userID = std::move(userID.value()),
        .userLogin = std::move(userLogin.value()),
        .userName = std::move(userName.value()),
        .reason = std::move(reason.value()),
        .chatRulesCited = std::move(vchatRulesCited),
    };
}

boost::json::result_for<Clear, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<Clear> /* tag */,
    const boost::json::value & /* jvRoot */)
{
    return Clear{};
}

boost::json::result_for<EmoteOnly, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<EmoteOnly> /* tag */,
    const boost::json::value & /* jvRoot */)
{
    return EmoteOnly{};
}

boost::json::result_for<EmoteOnlyOff, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<EmoteOnlyOff> /* tag */,
    const boost::json::value & /* jvRoot */)
{
    return EmoteOnlyOff{};
}

boost::json::result_for<Uniquechat, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<Uniquechat> /* tag */,
    const boost::json::value & /* jvRoot */)
{
    return Uniquechat{};
}

boost::json::result_for<UniquechatOff, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<UniquechatOff> /* tag */,
    const boost::json::value & /* jvRoot */)
{
    return UniquechatOff{};
}

boost::json::result_for<Subscribers, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<Subscribers> /* tag */,
    const boost::json::value & /* jvRoot */)
{
    return Subscribers{};
}

boost::json::result_for<SubscribersOff, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<SubscribersOff> /* tag */,
    const boost::json::value & /* jvRoot */)
{
    return SubscribersOff{};
}

boost::json::result_for<Event, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<Event> /* tag */,
    const boost::json::value &jvRoot)
{
    if (!jvRoot.is_object())
    {
        EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
    }
    const auto &root = jvRoot.get_object();

    const auto *jvbroadcasterUserID = root.if_contains("broadcaster_user_id");
    if (jvbroadcasterUserID == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto broadcasterUserID =
        boost::json::try_value_to<String>(*jvbroadcasterUserID);

    if (broadcasterUserID.has_error())
    {
        return broadcasterUserID.error();
    }

    const auto *jvbroadcasterUserLogin =
        root.if_contains("broadcaster_user_login");
    if (jvbroadcasterUserLogin == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto broadcasterUserLogin =
        boost::json::try_value_to<String>(*jvbroadcasterUserLogin);

    if (broadcasterUserLogin.has_error())
    {
        return broadcasterUserLogin.error();
    }

    const auto *jvbroadcasterUserName =
        root.if_contains("broadcaster_user_name");
    if (jvbroadcasterUserName == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto broadcasterUserName =
        boost::json::try_value_to<String>(*jvbroadcasterUserName);

    if (broadcasterUserName.has_error())
    {
        return broadcasterUserName.error();
    }

    std::optional<chatterino::eventsub::lib::String> sourceBroadcasterUserID =
        std::nullopt;
    const auto *jvsourceBroadcasterUserID =
        root.if_contains("source_broadcaster_user_id");
    if (jvsourceBroadcasterUserID != nullptr &&
        !jvsourceBroadcasterUserID->is_null())
    {
        auto tsourceBroadcasterUserID =
            boost::json::try_value_to<chatterino::eventsub::lib::String>(
                *jvsourceBroadcasterUserID);

        if (tsourceBroadcasterUserID.has_error())
        {
            return tsourceBroadcasterUserID.error();
        }
        sourceBroadcasterUserID = std::move(tsourceBroadcasterUserID.value());
    }

    std::optional<chatterino::eventsub::lib::String>
        sourceBroadcasterUserLogin = std::nullopt;
    const auto *jvsourceBroadcasterUserLogin =
        root.if_contains("source_broadcaster_user_login");
    if (jvsourceBroadcasterUserLogin != nullptr &&
        !jvsourceBroadcasterUserLogin->is_null())
    {
        auto tsourceBroadcasterUserLogin =
            boost::json::try_value_to<chatterino::eventsub::lib::String>(
                *jvsourceBroadcasterUserLogin);

        if (tsourceBroadcasterUserLogin.has_error())
        {
            return tsourceBroadcasterUserLogin.error();
        }
        sourceBroadcasterUserLogin =
            std::move(tsourceBroadcasterUserLogin.value());
    }

    std::optional<chatterino::eventsub::lib::String> sourceBroadcasterUserName =
        std::nullopt;
    const auto *jvsourceBroadcasterUserName =
        root.if_contains("source_broadcaster_user_name");
    if (jvsourceBroadcasterUserName != nullptr &&
        !jvsourceBroadcasterUserName->is_null())
    {
        auto tsourceBroadcasterUserName =
            boost::json::try_value_to<chatterino::eventsub::lib::String>(
                *jvsourceBroadcasterUserName);

        if (tsourceBroadcasterUserName.has_error())
        {
            return tsourceBroadcasterUserName.error();
        }
        sourceBroadcasterUserName =
            std::move(tsourceBroadcasterUserName.value());
    }

    const auto *jvmoderatorUserID = root.if_contains("moderator_user_id");
    if (jvmoderatorUserID == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto moderatorUserID =
        boost::json::try_value_to<String>(*jvmoderatorUserID);

    if (moderatorUserID.has_error())
    {
        return moderatorUserID.error();
    }

    const auto *jvmoderatorUserLogin = root.if_contains("moderator_user_login");
    if (jvmoderatorUserLogin == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto moderatorUserLogin =
        boost::json::try_value_to<String>(*jvmoderatorUserLogin);

    if (moderatorUserLogin.has_error())
    {
        return moderatorUserLogin.error();
    }

    const auto *jvmoderatorUserName = root.if_contains("moderator_user_name");
    if (jvmoderatorUserName == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto moderatorUserName =
        boost::json::try_value_to<String>(*jvmoderatorUserName);

    if (moderatorUserName.has_error())
    {
        return moderatorUserName.error();
    }

    const auto *jvactionTag = root.if_contains("action");
    if (jvactionTag == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto actionTagRes =
        boost::json::try_value_to<boost::json::string>(*jvactionTag);
    if (actionTagRes.has_error())
    {
        return actionTagRes.error();
    }
    std::string_view actionTag = *actionTagRes;
    decltype(std::declval<Event>().action) action;
    if (actionTag == Ban::TAG)
    {
        const auto *actionVal = root.if_contains(detail::fieldFor<Ban>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionBan = boost::json::try_value_to<Ban>(*actionVal);
        if (actionBan.has_error())
        {
            return actionBan.error();
        }
        action.emplace<Ban>(std::move(actionBan.value()));
    }
    else if (actionTag == Timeout::TAG)
    {
        const auto *actionVal = root.if_contains(detail::fieldFor<Timeout>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionTimeout = boost::json::try_value_to<Timeout>(*actionVal);
        if (actionTimeout.has_error())
        {
            return actionTimeout.error();
        }
        action.emplace<Timeout>(std::move(actionTimeout.value()));
    }
    else if (actionTag == Unban::TAG)
    {
        const auto *actionVal = root.if_contains(detail::fieldFor<Unban>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionUnban = boost::json::try_value_to<Unban>(*actionVal);
        if (actionUnban.has_error())
        {
            return actionUnban.error();
        }
        action.emplace<Unban>(std::move(actionUnban.value()));
    }
    else if (actionTag == Untimeout::TAG)
    {
        const auto *actionVal = root.if_contains(detail::fieldFor<Untimeout>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionUntimeout = boost::json::try_value_to<Untimeout>(*actionVal);
        if (actionUntimeout.has_error())
        {
            return actionUntimeout.error();
        }
        action.emplace<Untimeout>(std::move(actionUntimeout.value()));
    }
    else if (actionTag == Clear::TAG)
    {
        action.emplace<Clear>();
    }
    else if (actionTag == EmoteOnly::TAG)
    {
        action.emplace<EmoteOnly>();
    }
    else if (actionTag == EmoteOnlyOff::TAG)
    {
        action.emplace<EmoteOnlyOff>();
    }
    else if (actionTag == Followers::TAG)
    {
        const auto *actionVal = root.if_contains(detail::fieldFor<Followers>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionFollowers = boost::json::try_value_to<Followers>(*actionVal);
        if (actionFollowers.has_error())
        {
            return actionFollowers.error();
        }
        action.emplace<Followers>(actionFollowers.value());
    }
    else if (actionTag == FollowersOff::TAG)
    {
        action.emplace<FollowersOff>();
    }
    else if (actionTag == Uniquechat::TAG)
    {
        action.emplace<Uniquechat>();
    }
    else if (actionTag == UniquechatOff::TAG)
    {
        action.emplace<UniquechatOff>();
    }
    else if (actionTag == Slow::TAG)
    {
        const auto *actionVal = root.if_contains(detail::fieldFor<Slow>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionSlow = boost::json::try_value_to<Slow>(*actionVal);
        if (actionSlow.has_error())
        {
            return actionSlow.error();
        }
        action.emplace<Slow>(actionSlow.value());
    }
    else if (actionTag == SlowOff::TAG)
    {
        action.emplace<SlowOff>();
    }
    else if (actionTag == Subscribers::TAG)
    {
        action.emplace<Subscribers>();
    }
    else if (actionTag == SubscribersOff::TAG)
    {
        action.emplace<SubscribersOff>();
    }
    else if (actionTag == Unraid::TAG)
    {
        const auto *actionVal = root.if_contains(detail::fieldFor<Unraid>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionUnraid = boost::json::try_value_to<Unraid>(*actionVal);
        if (actionUnraid.has_error())
        {
            return actionUnraid.error();
        }
        action.emplace<Unraid>(std::move(actionUnraid.value()));
    }
    else if (actionTag == Delete::TAG)
    {
        const auto *actionVal = root.if_contains(detail::fieldFor<Delete>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionDelete = boost::json::try_value_to<Delete>(*actionVal);
        if (actionDelete.has_error())
        {
            return actionDelete.error();
        }
        action.emplace<Delete>(std::move(actionDelete.value()));
    }
    else if (actionTag == Unvip::TAG)
    {
        const auto *actionVal = root.if_contains(detail::fieldFor<Unvip>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionUnvip = boost::json::try_value_to<Unvip>(*actionVal);
        if (actionUnvip.has_error())
        {
            return actionUnvip.error();
        }
        action.emplace<Unvip>(std::move(actionUnvip.value()));
    }
    else if (actionTag == Vip::TAG)
    {
        const auto *actionVal = root.if_contains(detail::fieldFor<Vip>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionVip = boost::json::try_value_to<Vip>(*actionVal);
        if (actionVip.has_error())
        {
            return actionVip.error();
        }
        action.emplace<Vip>(std::move(actionVip.value()));
    }
    else if (actionTag == Raid::TAG)
    {
        const auto *actionVal = root.if_contains(detail::fieldFor<Raid>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionRaid = boost::json::try_value_to<Raid>(*actionVal);
        if (actionRaid.has_error())
        {
            return actionRaid.error();
        }
        action.emplace<Raid>(std::move(actionRaid.value()));
    }
    else if (actionTag == AddBlockedTerm::TAG)
    {
        const auto *actionVal =
            root.if_contains(detail::fieldFor<AddBlockedTerm>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionAddBlockedTerm =
            boost::json::try_value_to<AddBlockedTerm>(*actionVal);
        if (actionAddBlockedTerm.has_error())
        {
            return actionAddBlockedTerm.error();
        }
        action.emplace<AddBlockedTerm>(std::move(actionAddBlockedTerm.value()));
    }
    else if (actionTag == AddPermittedTerm::TAG)
    {
        const auto *actionVal =
            root.if_contains(detail::fieldFor<AddPermittedTerm>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionAddPermittedTerm =
            boost::json::try_value_to<AddPermittedTerm>(*actionVal);
        if (actionAddPermittedTerm.has_error())
        {
            return actionAddPermittedTerm.error();
        }
        action.emplace<AddPermittedTerm>(
            std::move(actionAddPermittedTerm.value()));
    }
    else if (actionTag == RemoveBlockedTerm::TAG)
    {
        const auto *actionVal =
            root.if_contains(detail::fieldFor<RemoveBlockedTerm>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionRemoveBlockedTerm =
            boost::json::try_value_to<RemoveBlockedTerm>(*actionVal);
        if (actionRemoveBlockedTerm.has_error())
        {
            return actionRemoveBlockedTerm.error();
        }
        action.emplace<RemoveBlockedTerm>(
            std::move(actionRemoveBlockedTerm.value()));
    }
    else if (actionTag == RemovePermittedTerm::TAG)
    {
        const auto *actionVal =
            root.if_contains(detail::fieldFor<RemovePermittedTerm>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionRemovePermittedTerm =
            boost::json::try_value_to<RemovePermittedTerm>(*actionVal);
        if (actionRemovePermittedTerm.has_error())
        {
            return actionRemovePermittedTerm.error();
        }
        action.emplace<RemovePermittedTerm>(
            std::move(actionRemovePermittedTerm.value()));
    }
    else if (actionTag == Mod::TAG)
    {
        const auto *actionVal = root.if_contains(detail::fieldFor<Mod>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionMod = boost::json::try_value_to<Mod>(*actionVal);
        if (actionMod.has_error())
        {
            return actionMod.error();
        }
        action.emplace<Mod>(std::move(actionMod.value()));
    }
    else if (actionTag == Unmod::TAG)
    {
        const auto *actionVal = root.if_contains(detail::fieldFor<Unmod>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionUnmod = boost::json::try_value_to<Unmod>(*actionVal);
        if (actionUnmod.has_error())
        {
            return actionUnmod.error();
        }
        action.emplace<Unmod>(std::move(actionUnmod.value()));
    }
    else if (actionTag == ApproveUnbanRequest::TAG)
    {
        const auto *actionVal =
            root.if_contains(detail::fieldFor<ApproveUnbanRequest>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionApproveUnbanRequest =
            boost::json::try_value_to<ApproveUnbanRequest>(*actionVal);
        if (actionApproveUnbanRequest.has_error())
        {
            return actionApproveUnbanRequest.error();
        }
        action.emplace<ApproveUnbanRequest>(
            std::move(actionApproveUnbanRequest.value()));
    }
    else if (actionTag == DenyUnbanRequest::TAG)
    {
        const auto *actionVal =
            root.if_contains(detail::fieldFor<DenyUnbanRequest>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionDenyUnbanRequest =
            boost::json::try_value_to<DenyUnbanRequest>(*actionVal);
        if (actionDenyUnbanRequest.has_error())
        {
            return actionDenyUnbanRequest.error();
        }
        action.emplace<DenyUnbanRequest>(
            std::move(actionDenyUnbanRequest.value()));
    }
    else if (actionTag == Warn::TAG)
    {
        const auto *actionVal = root.if_contains(detail::fieldFor<Warn>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionWarn = boost::json::try_value_to<Warn>(*actionVal);
        if (actionWarn.has_error())
        {
            return actionWarn.error();
        }
        action.emplace<Warn>(std::move(actionWarn.value()));
    }
    else if (actionTag == SharedChatBan::TAG)
    {
        const auto *actionVal =
            root.if_contains(detail::fieldFor<SharedChatBan>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionSharedChatBan =
            boost::json::try_value_to<SharedChatBan>(*actionVal);
        if (actionSharedChatBan.has_error())
        {
            return actionSharedChatBan.error();
        }
        action.emplace<SharedChatBan>(std::move(actionSharedChatBan.value()));
    }
    else if (actionTag == SharedChatTimeout::TAG)
    {
        const auto *actionVal =
            root.if_contains(detail::fieldFor<SharedChatTimeout>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionSharedChatTimeout =
            boost::json::try_value_to<SharedChatTimeout>(*actionVal);
        if (actionSharedChatTimeout.has_error())
        {
            return actionSharedChatTimeout.error();
        }
        action.emplace<SharedChatTimeout>(
            std::move(actionSharedChatTimeout.value()));
    }
    else if (actionTag == SharedChatUnban::TAG)
    {
        const auto *actionVal =
            root.if_contains(detail::fieldFor<SharedChatUnban>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionSharedChatUnban =
            boost::json::try_value_to<SharedChatUnban>(*actionVal);
        if (actionSharedChatUnban.has_error())
        {
            return actionSharedChatUnban.error();
        }
        action.emplace<SharedChatUnban>(
            std::move(actionSharedChatUnban.value()));
    }
    else if (actionTag == SharedChatUntimeout::TAG)
    {
        const auto *actionVal =
            root.if_contains(detail::fieldFor<SharedChatUntimeout>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionSharedChatUntimeout =
            boost::json::try_value_to<SharedChatUntimeout>(*actionVal);
        if (actionSharedChatUntimeout.has_error())
        {
            return actionSharedChatUntimeout.error();
        }
        action.emplace<SharedChatUntimeout>(
            std::move(actionSharedChatUntimeout.value()));
    }
    else if (actionTag == SharedChatDelete::TAG)
    {
        const auto *actionVal =
            root.if_contains(detail::fieldFor<SharedChatDelete>());
        if (!actionVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto actionSharedChatDelete =
            boost::json::try_value_to<SharedChatDelete>(*actionVal);
        if (actionSharedChatDelete.has_error())
        {
            return actionSharedChatDelete.error();
        }
        action.emplace<SharedChatDelete>(
            std::move(actionSharedChatDelete.value()));
    }
    else
    {
        action.emplace<std::string>(actionTag);
    }

    return Event{
        .broadcasterUserID = std::move(broadcasterUserID.value()),
        .broadcasterUserLogin = std::move(broadcasterUserLogin.value()),
        .broadcasterUserName = std::move(broadcasterUserName.value()),
        .sourceBroadcasterUserID = std::move(sourceBroadcasterUserID),
        .sourceBroadcasterUserLogin = std::move(sourceBroadcasterUserLogin),
        .sourceBroadcasterUserName = std::move(sourceBroadcasterUserName),
        .moderatorUserID = std::move(moderatorUserID.value()),
        .moderatorUserLogin = std::move(moderatorUserLogin.value()),
        .moderatorUserName = std::move(moderatorUserName.value()),
        .action = std::move(action),
    };
}

boost::json::result_for<Payload, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<Payload> /* tag */,
    const boost::json::value &jvRoot)
{
    if (!jvRoot.is_object())
    {
        EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
    }
    const auto &root = jvRoot.get_object();

    const auto *jvsubscription = root.if_contains("subscription");
    if (jvsubscription == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto subscription =
        boost::json::try_value_to<subscription::Subscription>(*jvsubscription);

    if (subscription.has_error())
    {
        return subscription.error();
    }

    const auto *jvevent = root.if_contains("event");
    if (jvevent == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto event = boost::json::try_value_to<Event>(*jvevent);

    if (event.has_error())
    {
        return event.error();
    }

    return Payload{
        .subscription = std::move(subscription.value()),
        .event = std::move(event.value()),
    };
}

}  // namespace chatterino::eventsub::lib::payload::channel_moderate::v2
