// WARNING: This file is automatically generated. Any changes will be lost.
#include "twitch-eventsub-ws/payloads/channel-chat-notification-v1.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_chat_notification::v1 {

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

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

    auto setID = boost::json::try_value_to<std::string>(*jvsetID);

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

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

    auto id = boost::json::try_value_to<std::string>(*jvid);

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

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

    auto info = boost::json::try_value_to<std::string>(*jvinfo);

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

    return Badge{
        .setID = std::move(setID.value()),
        .id = std::move(id.value()),
        .info = std::move(info.value()),
    };
}

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

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

    auto subTier = boost::json::try_value_to<std::string>(*jvsubTier);

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

    static_assert(std::is_trivially_copyable_v<std::remove_reference_t<
                      decltype(std::declval<Subcription>().isPrime)>>);
    const auto *jvisPrime = root.if_contains("is_prime");
    if (jvisPrime == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto isPrime = boost::json::try_value_to<bool>(*jvisPrime);

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

    static_assert(std::is_trivially_copyable_v<std::remove_reference_t<
                      decltype(std::declval<Subcription>().durationMonths)>>);
    const auto *jvdurationMonths = root.if_contains("duration_months");
    if (jvdurationMonths == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto durationMonths = boost::json::try_value_to<int>(*jvdurationMonths);

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

    return Subcription{
        .subTier = std::move(subTier.value()),
        .isPrime = isPrime.value(),
        .durationMonths = durationMonths.value(),
    };
}

boost::json::result_for<Resubscription, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<Resubscription> /* 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<Resubscription>().cumulativeMonths)>>);
    const auto *jvcumulativeMonths = root.if_contains("cumulative_months");
    if (jvcumulativeMonths == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto cumulativeMonths = boost::json::try_value_to<int>(*jvcumulativeMonths);

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

    static_assert(
        std::is_trivially_copyable_v<std::remove_reference_t<
            decltype(std::declval<Resubscription>().durationMonths)>>);
    const auto *jvdurationMonths = root.if_contains("duration_months");
    if (jvdurationMonths == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto durationMonths = boost::json::try_value_to<int>(*jvdurationMonths);

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

    static_assert(std::is_trivially_copyable_v<std::remove_reference_t<
                      decltype(std::declval<Resubscription>().streakMonths)>>);
    std::optional<int> streakMonths = std::nullopt;
    const auto *jvstreakMonths = root.if_contains("streak_months");
    if (jvstreakMonths != nullptr && !jvstreakMonths->is_null())
    {
        auto tstreakMonths = boost::json::try_value_to<int>(*jvstreakMonths);

        if (tstreakMonths.has_error())
        {
            return tstreakMonths.error();
        }
        streakMonths = tstreakMonths.value();
    }

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

    auto subTier = boost::json::try_value_to<std::string>(*jvsubTier);

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

    static_assert(std::is_trivially_copyable_v<std::remove_reference_t<
                      decltype(std::declval<Resubscription>().isPrime)>>);
    std::optional<bool> isPrime = std::nullopt;
    const auto *jvisPrime = root.if_contains("is_prime");
    if (jvisPrime != nullptr && !jvisPrime->is_null())
    {
        auto tisPrime = boost::json::try_value_to<bool>(*jvisPrime);

        if (tisPrime.has_error())
        {
            return tisPrime.error();
        }
        isPrime = tisPrime.value();
    }

    static_assert(std::is_trivially_copyable_v<std::remove_reference_t<
                      decltype(std::declval<Resubscription>().isGift)>>);
    const auto *jvisGift = root.if_contains("is_gift");
    if (jvisGift == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto isGift = boost::json::try_value_to<bool>(*jvisGift);

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

    static_assert(
        std::is_trivially_copyable_v<std::remove_reference_t<
            decltype(std::declval<Resubscription>().gifterIsAnonymous)>>);
    std::optional<bool> gifterIsAnonymous = std::nullopt;
    const auto *jvgifterIsAnonymous = root.if_contains("gifter_is_anonymous");
    if (jvgifterIsAnonymous != nullptr && !jvgifterIsAnonymous->is_null())
    {
        auto tgifterIsAnonymous =
            boost::json::try_value_to<bool>(*jvgifterIsAnonymous);

        if (tgifterIsAnonymous.has_error())
        {
            return tgifterIsAnonymous.error();
        }
        gifterIsAnonymous = tgifterIsAnonymous.value();
    }

    std::optional<std::string> gifterUserID = std::nullopt;
    const auto *jvgifterUserID = root.if_contains("gifter_user_id");
    if (jvgifterUserID != nullptr && !jvgifterUserID->is_null())
    {
        auto tgifterUserID =
            boost::json::try_value_to<std::string>(*jvgifterUserID);

        if (tgifterUserID.has_error())
        {
            return tgifterUserID.error();
        }
        gifterUserID = std::move(tgifterUserID.value());
    }

    std::optional<std::string> gifterUserName = std::nullopt;
    const auto *jvgifterUserName = root.if_contains("gifter_user_name");
    if (jvgifterUserName != nullptr && !jvgifterUserName->is_null())
    {
        auto tgifterUserName =
            boost::json::try_value_to<std::string>(*jvgifterUserName);

        if (tgifterUserName.has_error())
        {
            return tgifterUserName.error();
        }
        gifterUserName = std::move(tgifterUserName.value());
    }

    std::optional<std::string> gifterUserLogin = std::nullopt;
    const auto *jvgifterUserLogin = root.if_contains("gifter_user_login");
    if (jvgifterUserLogin != nullptr && !jvgifterUserLogin->is_null())
    {
        auto tgifterUserLogin =
            boost::json::try_value_to<std::string>(*jvgifterUserLogin);

        if (tgifterUserLogin.has_error())
        {
            return tgifterUserLogin.error();
        }
        gifterUserLogin = std::move(tgifterUserLogin.value());
    }

    return Resubscription{
        .cumulativeMonths = cumulativeMonths.value(),
        .durationMonths = durationMonths.value(),
        .streakMonths = streakMonths,
        .subTier = std::move(subTier.value()),
        .isPrime = isPrime,
        .isGift = isGift.value(),
        .gifterIsAnonymous = gifterIsAnonymous,
        .gifterUserID = std::move(gifterUserID),
        .gifterUserName = std::move(gifterUserName),
        .gifterUserLogin = std::move(gifterUserLogin),
    };
}

boost::json::result_for<GiftSubscription, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<GiftSubscription> /* 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<GiftSubscription>().durationMonths)>>);
    const auto *jvdurationMonths = root.if_contains("duration_months");
    if (jvdurationMonths == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto durationMonths = boost::json::try_value_to<int>(*jvdurationMonths);

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

    static_assert(
        std::is_trivially_copyable_v<std::remove_reference_t<
            decltype(std::declval<GiftSubscription>().cumulativeTotal)>>);
    std::optional<int> cumulativeTotal = std::nullopt;
    const auto *jvcumulativeTotal = root.if_contains("cumulative_total");
    if (jvcumulativeTotal != nullptr && !jvcumulativeTotal->is_null())
    {
        auto tcumulativeTotal =
            boost::json::try_value_to<int>(*jvcumulativeTotal);

        if (tcumulativeTotal.has_error())
        {
            return tcumulativeTotal.error();
        }
        cumulativeTotal = tcumulativeTotal.value();
    }

    static_assert(
        std::is_trivially_copyable_v<std::remove_reference_t<
            decltype(std::declval<GiftSubscription>().streakMonths)>>);
    std::optional<int> streakMonths = std::nullopt;
    const auto *jvstreakMonths = root.if_contains("streak_months");
    if (jvstreakMonths != nullptr && !jvstreakMonths->is_null())
    {
        auto tstreakMonths = boost::json::try_value_to<int>(*jvstreakMonths);

        if (tstreakMonths.has_error())
        {
            return tstreakMonths.error();
        }
        streakMonths = tstreakMonths.value();
    }

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

    auto recipientUserID =
        boost::json::try_value_to<std::string>(*jvrecipientUserID);

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

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

    auto recipientUserName =
        boost::json::try_value_to<std::string>(*jvrecipientUserName);

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

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

    auto recipientUserLogin =
        boost::json::try_value_to<std::string>(*jvrecipientUserLogin);

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

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

    auto subTier = boost::json::try_value_to<std::string>(*jvsubTier);

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

    std::optional<std::string> communityGiftID = std::nullopt;
    const auto *jvcommunityGiftID = root.if_contains("community_gift_id");
    if (jvcommunityGiftID != nullptr && !jvcommunityGiftID->is_null())
    {
        auto tcommunityGiftID =
            boost::json::try_value_to<std::string>(*jvcommunityGiftID);

        if (tcommunityGiftID.has_error())
        {
            return tcommunityGiftID.error();
        }
        communityGiftID = std::move(tcommunityGiftID.value());
    }

    return GiftSubscription{
        .durationMonths = durationMonths.value(),
        .cumulativeTotal = cumulativeTotal,
        .streakMonths = streakMonths,
        .recipientUserID = std::move(recipientUserID.value()),
        .recipientUserName = std::move(recipientUserName.value()),
        .recipientUserLogin = std::move(recipientUserLogin.value()),
        .subTier = std::move(subTier.value()),
        .communityGiftID = std::move(communityGiftID),
    };
}

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

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

    auto id = boost::json::try_value_to<std::string>(*jvid);

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

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

    auto total = boost::json::try_value_to<int>(*jvtotal);

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

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

    auto subTier = boost::json::try_value_to<std::string>(*jvsubTier);

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

    static_assert(std::is_trivially_copyable_v<std::remove_reference_t<
                      decltype(std::declval<CommunityGiftSubscription>()
                                   .cumulativeTotal)>>);
    std::optional<int> cumulativeTotal = std::nullopt;
    const auto *jvcumulativeTotal = root.if_contains("cumulative_total");
    if (jvcumulativeTotal != nullptr && !jvcumulativeTotal->is_null())
    {
        auto tcumulativeTotal =
            boost::json::try_value_to<int>(*jvcumulativeTotal);

        if (tcumulativeTotal.has_error())
        {
            return tcumulativeTotal.error();
        }
        cumulativeTotal = tcumulativeTotal.value();
    }

    return CommunityGiftSubscription{
        .id = std::move(id.value()),
        .total = total.value(),
        .subTier = std::move(subTier.value()),
        .cumulativeTotal = cumulativeTotal,
    };
}

boost::json::result_for<GiftPaidUpgrade, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<GiftPaidUpgrade> /* 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<GiftPaidUpgrade>().gifterIsAnonymous)>>);
    const auto *jvgifterIsAnonymous = root.if_contains("gifter_is_anonymous");
    if (jvgifterIsAnonymous == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto gifterIsAnonymous =
        boost::json::try_value_to<bool>(*jvgifterIsAnonymous);

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

    std::optional<std::string> gifterUserID = std::nullopt;
    const auto *jvgifterUserID = root.if_contains("gifter_user_id");
    if (jvgifterUserID != nullptr && !jvgifterUserID->is_null())
    {
        auto tgifterUserID =
            boost::json::try_value_to<std::string>(*jvgifterUserID);

        if (tgifterUserID.has_error())
        {
            return tgifterUserID.error();
        }
        gifterUserID = std::move(tgifterUserID.value());
    }

    std::optional<std::string> gifterUserName = std::nullopt;
    const auto *jvgifterUserName = root.if_contains("gifter_user_name");
    if (jvgifterUserName != nullptr && !jvgifterUserName->is_null())
    {
        auto tgifterUserName =
            boost::json::try_value_to<std::string>(*jvgifterUserName);

        if (tgifterUserName.has_error())
        {
            return tgifterUserName.error();
        }
        gifterUserName = std::move(tgifterUserName.value());
    }

    std::optional<std::string> gifterUserLogin = std::nullopt;
    const auto *jvgifterUserLogin = root.if_contains("gifter_user_login");
    if (jvgifterUserLogin != nullptr && !jvgifterUserLogin->is_null())
    {
        auto tgifterUserLogin =
            boost::json::try_value_to<std::string>(*jvgifterUserLogin);

        if (tgifterUserLogin.has_error())
        {
            return tgifterUserLogin.error();
        }
        gifterUserLogin = std::move(tgifterUserLogin.value());
    }

    return GiftPaidUpgrade{
        .gifterIsAnonymous = gifterIsAnonymous.value(),
        .gifterUserID = std::move(gifterUserID),
        .gifterUserName = std::move(gifterUserName),
        .gifterUserLogin = std::move(gifterUserLogin),
    };
}

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

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

    auto subTier = boost::json::try_value_to<std::string>(*jvsubTier);

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

    return PrimePaidUpgrade{
        .subTier = std::move(subTier.value()),
    };
}

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<std::string>(*jvuserID);

    if (userID.has_error())
    {
        return userID.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<std::string>(*jvuserName);

    if (userName.has_error())
    {
        return userName.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<std::string>(*jvuserLogin);

    if (userLogin.has_error())
    {
        return userLogin.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();
    }

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

    auto profileImageURL =
        boost::json::try_value_to<std::string>(*jvprofileImageURL);

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

    return Raid{
        .userID = std::move(userID.value()),
        .userName = std::move(userName.value()),
        .userLogin = std::move(userLogin.value()),
        .viewerCount = viewerCount.value(),
        .profileImageURL = std::move(profileImageURL.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 */)
{
    return Unraid{};
}

boost::json::result_for<PayItForward, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<PayItForward> /* 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<PayItForward>().gifterIsAnonymous)>>);
    const auto *jvgifterIsAnonymous = root.if_contains("gifter_is_anonymous");
    if (jvgifterIsAnonymous == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto gifterIsAnonymous =
        boost::json::try_value_to<bool>(*jvgifterIsAnonymous);

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

    std::optional<std::string> gifterUserID = std::nullopt;
    const auto *jvgifterUserID = root.if_contains("gifter_user_id");
    if (jvgifterUserID != nullptr && !jvgifterUserID->is_null())
    {
        auto tgifterUserID =
            boost::json::try_value_to<std::string>(*jvgifterUserID);

        if (tgifterUserID.has_error())
        {
            return tgifterUserID.error();
        }
        gifterUserID = std::move(tgifterUserID.value());
    }

    std::optional<std::string> gifterUserName = std::nullopt;
    const auto *jvgifterUserName = root.if_contains("gifter_user_name");
    if (jvgifterUserName != nullptr && !jvgifterUserName->is_null())
    {
        auto tgifterUserName =
            boost::json::try_value_to<std::string>(*jvgifterUserName);

        if (tgifterUserName.has_error())
        {
            return tgifterUserName.error();
        }
        gifterUserName = std::move(tgifterUserName.value());
    }

    std::optional<std::string> gifterUserLogin = std::nullopt;
    const auto *jvgifterUserLogin = root.if_contains("gifter_user_login");
    if (jvgifterUserLogin != nullptr && !jvgifterUserLogin->is_null())
    {
        auto tgifterUserLogin =
            boost::json::try_value_to<std::string>(*jvgifterUserLogin);

        if (tgifterUserLogin.has_error())
        {
            return tgifterUserLogin.error();
        }
        gifterUserLogin = std::move(tgifterUserLogin.value());
    }

    return PayItForward{
        .gifterIsAnonymous = gifterIsAnonymous.value(),
        .gifterUserID = std::move(gifterUserID),
        .gifterUserName = std::move(gifterUserName),
        .gifterUserLogin = std::move(gifterUserLogin),
    };
}

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

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

    auto color = boost::json::try_value_to<std::string>(*jvcolor);

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

    return Announcement{
        .color = std::move(color.value()),
    };
}

boost::json::result_for<CharityDonationAmount, boost::json::value>::type
    tag_invoke(boost::json::try_value_to_tag<CharityDonationAmount> /* 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<CharityDonationAmount>().value)>>);
    const auto *jvvalue = root.if_contains("value");
    if (jvvalue == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto value = boost::json::try_value_to<int>(*jvvalue);

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

    static_assert(
        std::is_trivially_copyable_v<std::remove_reference_t<
            decltype(std::declval<CharityDonationAmount>().decimalPlaces)>>);
    const auto *jvdecimalPlaces = root.if_contains("decimal_places");
    if (jvdecimalPlaces == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto decimalPlaces = boost::json::try_value_to<int>(*jvdecimalPlaces);

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

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

    auto currency = boost::json::try_value_to<std::string>(*jvcurrency);

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

    return CharityDonationAmount{
        .value = value.value(),
        .decimalPlaces = decimalPlaces.value(),
        .currency = std::move(currency.value()),
    };
}

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

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

    auto charityName = boost::json::try_value_to<std::string>(*jvcharityName);

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

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

    auto amount = boost::json::try_value_to<CharityDonationAmount>(*jvamount);

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

    return CharityDonation{
        .charityName = std::move(charityName.value()),
        .amount = std::move(amount.value()),
    };
}

boost::json::result_for<BitsBadgeTier, boost::json::value>::type tag_invoke(
    boost::json::try_value_to_tag<BitsBadgeTier> /* 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<BitsBadgeTier>().tier)>>);
    const auto *jvtier = root.if_contains("tier");
    if (jvtier == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto tier = boost::json::try_value_to<int>(*jvtier);

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

    return BitsBadgeTier{
        .tier = tier.value(),
    };
}

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<std::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<std::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<std::string>(*jvbroadcasterUserName);

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

    std::optional<std::string> chatterUserID = std::nullopt;
    const auto *jvchatterUserID = root.if_contains("chatter_user_id");
    if (jvchatterUserID != nullptr && !jvchatterUserID->is_null())
    {
        auto tchatterUserID =
            boost::json::try_value_to<std::string>(*jvchatterUserID);

        if (tchatterUserID.has_error())
        {
            return tchatterUserID.error();
        }
        chatterUserID = std::move(tchatterUserID.value());
    }

    std::optional<std::string> chatterUserLogin = std::nullopt;
    const auto *jvchatterUserLogin = root.if_contains("chatter_user_login");
    if (jvchatterUserLogin != nullptr && !jvchatterUserLogin->is_null())
    {
        auto tchatterUserLogin =
            boost::json::try_value_to<std::string>(*jvchatterUserLogin);

        if (tchatterUserLogin.has_error())
        {
            return tchatterUserLogin.error();
        }
        chatterUserLogin = std::move(tchatterUserLogin.value());
    }

    std::optional<std::string> chatterUserName = std::nullopt;
    const auto *jvchatterUserName = root.if_contains("chatter_user_name");
    if (jvchatterUserName != nullptr && !jvchatterUserName->is_null())
    {
        auto tchatterUserName =
            boost::json::try_value_to<std::string>(*jvchatterUserName);

        if (tchatterUserName.has_error())
        {
            return tchatterUserName.error();
        }
        chatterUserName = std::move(tchatterUserName.value());
    }

    static_assert(std::is_trivially_copyable_v<std::remove_reference_t<
                      decltype(std::declval<Event>().chatterIsAnonymous)>>);
    const auto *jvchatterIsAnonymous = root.if_contains("chatter_is_anonymous");
    if (jvchatterIsAnonymous == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto chatterIsAnonymous =
        boost::json::try_value_to<bool>(*jvchatterIsAnonymous);

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

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

    auto color = boost::json::try_value_to<std::string>(*jvcolor);

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

    std::vector<Badge> vbadges;
    const auto *jvbadges = root.if_contains("badges");
    if (jvbadges != nullptr && !jvbadges->is_null())
    {
        auto badges = boost::json::try_value_to<std::vector<Badge>>(*jvbadges);
        if (badges.has_error())
        {
            return badges.error();
        }
        else
        {
            vbadges = std::move(badges.value());
        }
    }
    const auto *jvsystemMessage = root.if_contains("system_message");
    if (jvsystemMessage == nullptr)
    {
        EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
    }

    auto systemMessage =
        boost::json::try_value_to<std::string>(*jvsystemMessage);

    if (systemMessage.has_error())
    {
        return systemMessage.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<std::string>(*jvmessageID);

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

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

    auto message =
        boost::json::try_value_to<chatterino::eventsub::lib::chat::Message>(
            *jvmessage);

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

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

    auto innerTagRes =
        boost::json::try_value_to<boost::json::string>(*jvinnerTag);
    if (innerTagRes.has_error())
    {
        return innerTagRes.error();
    }
    std::string_view innerTag = *innerTagRes;
    decltype(std::declval<Event>().inner) inner;
    if (innerTag == Subcription::TAG)
    {
        const auto *innerVal =
            root.if_contains(detail::fieldFor<Subcription>());
        if (!innerVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto innerSubcription =
            boost::json::try_value_to<Subcription>(*innerVal);
        if (innerSubcription.has_error())
        {
            return innerSubcription.error();
        }
        inner.emplace<Subcription>(std::move(innerSubcription.value()));
    }
    else if (innerTag == Resubscription::TAG)
    {
        const auto *innerVal =
            root.if_contains(detail::fieldFor<Resubscription>());
        if (!innerVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto innerResubscription =
            boost::json::try_value_to<Resubscription>(*innerVal);
        if (innerResubscription.has_error())
        {
            return innerResubscription.error();
        }
        inner.emplace<Resubscription>(std::move(innerResubscription.value()));
    }
    else if (innerTag == GiftSubscription::TAG)
    {
        const auto *innerVal =
            root.if_contains(detail::fieldFor<GiftSubscription>());
        if (!innerVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto innerGiftSubscription =
            boost::json::try_value_to<GiftSubscription>(*innerVal);
        if (innerGiftSubscription.has_error())
        {
            return innerGiftSubscription.error();
        }
        inner.emplace<GiftSubscription>(
            std::move(innerGiftSubscription.value()));
    }
    else if (innerTag == CommunityGiftSubscription::TAG)
    {
        const auto *innerVal =
            root.if_contains(detail::fieldFor<CommunityGiftSubscription>());
        if (!innerVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto innerCommunityGiftSubscription =
            boost::json::try_value_to<CommunityGiftSubscription>(*innerVal);
        if (innerCommunityGiftSubscription.has_error())
        {
            return innerCommunityGiftSubscription.error();
        }
        inner.emplace<CommunityGiftSubscription>(
            std::move(innerCommunityGiftSubscription.value()));
    }
    else if (innerTag == GiftPaidUpgrade::TAG)
    {
        const auto *innerVal =
            root.if_contains(detail::fieldFor<GiftPaidUpgrade>());
        if (!innerVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto innerGiftPaidUpgrade =
            boost::json::try_value_to<GiftPaidUpgrade>(*innerVal);
        if (innerGiftPaidUpgrade.has_error())
        {
            return innerGiftPaidUpgrade.error();
        }
        inner.emplace<GiftPaidUpgrade>(std::move(innerGiftPaidUpgrade.value()));
    }
    else if (innerTag == PrimePaidUpgrade::TAG)
    {
        const auto *innerVal =
            root.if_contains(detail::fieldFor<PrimePaidUpgrade>());
        if (!innerVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto innerPrimePaidUpgrade =
            boost::json::try_value_to<PrimePaidUpgrade>(*innerVal);
        if (innerPrimePaidUpgrade.has_error())
        {
            return innerPrimePaidUpgrade.error();
        }
        inner.emplace<PrimePaidUpgrade>(
            std::move(innerPrimePaidUpgrade.value()));
    }
    else if (innerTag == Raid::TAG)
    {
        const auto *innerVal = root.if_contains(detail::fieldFor<Raid>());
        if (!innerVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto innerRaid = boost::json::try_value_to<Raid>(*innerVal);
        if (innerRaid.has_error())
        {
            return innerRaid.error();
        }
        inner.emplace<Raid>(std::move(innerRaid.value()));
    }
    else if (innerTag == Unraid::TAG)
    {
        inner.emplace<Unraid>();
    }
    else if (innerTag == PayItForward::TAG)
    {
        const auto *innerVal =
            root.if_contains(detail::fieldFor<PayItForward>());
        if (!innerVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto innerPayItForward =
            boost::json::try_value_to<PayItForward>(*innerVal);
        if (innerPayItForward.has_error())
        {
            return innerPayItForward.error();
        }
        inner.emplace<PayItForward>(std::move(innerPayItForward.value()));
    }
    else if (innerTag == Announcement::TAG)
    {
        const auto *innerVal =
            root.if_contains(detail::fieldFor<Announcement>());
        if (!innerVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto innerAnnouncement =
            boost::json::try_value_to<Announcement>(*innerVal);
        if (innerAnnouncement.has_error())
        {
            return innerAnnouncement.error();
        }
        inner.emplace<Announcement>(std::move(innerAnnouncement.value()));
    }
    else if (innerTag == CharityDonation::TAG)
    {
        const auto *innerVal =
            root.if_contains(detail::fieldFor<CharityDonation>());
        if (!innerVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto innerCharityDonation =
            boost::json::try_value_to<CharityDonation>(*innerVal);
        if (innerCharityDonation.has_error())
        {
            return innerCharityDonation.error();
        }
        inner.emplace<CharityDonation>(std::move(innerCharityDonation.value()));
    }
    else if (innerTag == BitsBadgeTier::TAG)
    {
        const auto *innerVal =
            root.if_contains(detail::fieldFor<BitsBadgeTier>());
        if (!innerVal)
        {
            EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
        }
        auto innerBitsBadgeTier =
            boost::json::try_value_to<BitsBadgeTier>(*innerVal);
        if (innerBitsBadgeTier.has_error())
        {
            return innerBitsBadgeTier.error();
        }
        inner.emplace<BitsBadgeTier>(innerBitsBadgeTier.value());
    }
    else
    {
        inner.emplace<std::string>(innerTag);
    }

    return Event{
        .broadcasterUserID = std::move(broadcasterUserID.value()),
        .broadcasterUserLogin = std::move(broadcasterUserLogin.value()),
        .broadcasterUserName = std::move(broadcasterUserName.value()),
        .chatterUserID = std::move(chatterUserID),
        .chatterUserLogin = std::move(chatterUserLogin),
        .chatterUserName = std::move(chatterUserName),
        .chatterIsAnonymous = chatterIsAnonymous.value(),
        .color = std::move(color.value()),
        .badges = std::move(vbadges),
        .systemMessage = std::move(systemMessage.value()),
        .messageID = std::move(messageID.value()),
        .message = std::move(message.value()),
        .inner = std::move(inner),
    };
}

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<
        chatterino::eventsub::lib::payload::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_chat_notification::v1
