#include <AK/String.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/Iterator.h>
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/PrimitiveString.h>
#include <LibJS/Runtime/Realm.h>
#include <LibJS/Runtime/VM.h>
#include <LibJS/Runtime/Value.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibWeb/Bindings/EventTarget.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/Notification.h>
#include <LibWeb/NotificationsAPI/Notification.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
#include <LibWeb/WebIDL/Tracing.h>
#include <LibWeb/WebIDL/Types.h>

namespace Web::Bindings {

void NotificationConstructor::initialize(JS::Realm& realm, JS::NativeFunction& object)
{
    auto& vm = realm.vm();
    [[maybe_unused]] u8 default_attributes = JS::Attribute::Enumerable;

    object.set_prototype(&ensure_web_constructor<EventTargetPrototype>(realm, "EventTarget"_fly_string));
    object.define_direct_property(vm.names.length, JS::Value(1), JS::Attribute::Configurable);
    object.define_direct_property(vm.names.name, JS::PrimitiveString::create(vm, "Notification"_utf16), JS::Attribute::Configurable);
    object.define_direct_property(vm.names.prototype, &ensure_web_prototype<NotificationPrototype>(realm, "Notification"_fly_string), 0);
    object.define_native_accessor(realm, "maxActions"_utf16_fly_string, max_actions_getter, nullptr, default_attributes);
}

JS::ThrowCompletionOr<GC::Ref<JS::Object>> NotificationConstructor::construct([[maybe_unused]] InterfaceConstructor& constructor, [[maybe_unused]] JS::FunctionObject& new_target)
{
    WebIDL::log_trace(constructor.vm(), "NotificationConstructor::construct");
    auto& vm = constructor.vm();
    [[maybe_unused]] auto& realm = *vm.current_realm();

    // To internally create a new object implementing the interface Notification:

    // 3.2. Let prototype be ? Get(newTarget, "prototype").
    auto prototype = TRY(new_target.get(vm.names.prototype));

    // 3.3. If Type(prototype) is not Object, then:
    if (!prototype.is_object()) {
        // 1. Let targetRealm be ? GetFunctionRealm(newTarget).
        auto* target_realm = TRY(JS::get_function_realm(vm, new_target));

        // 2. Set prototype to the interface prototype object for interface in targetRealm.
        VERIFY(target_realm);
        prototype = &Bindings::ensure_web_prototype<NotificationPrototype>(*target_realm, "Notification"_fly_string);
    }

    if (vm.argument_count() < 1)
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::BadArgCountOne, "Notification");

    auto arg0 = vm.argument(0);
    auto title = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<String> {
        return TRY(WebIDL::to_string(vm, arg0));
    }(); }));

    auto arg1 = vm.argument(1);
    NotificationOptions options = NotificationOptions {};
    if (!arg1.is_undefined())
        options = TRY(throw_dom_exception_if_needed(vm, [&] { return convert_to_idl_value_for_notification_options(vm, arg1); }));

    auto impl = TRY(throw_dom_exception_if_needed(vm, [&] { return NotificationsAPI::Notification::construct_impl(realm, title, options); }));

    // 7. Set instance.[[Prototype]] to prototype.
    VERIFY(prototype.is_object());
    impl->set_prototype(&prototype.as_object());

    // FIXME: Steps 8...11. of the "internally create a new object implementing the interface Notification" algorithm
    // (https://webidl.spec.whatwg.org/#js-platform-objects) are currently not handled, or are handled within NotificationsAPI::Notification::construct_impl().

    return *impl;
}

void NotificationPrototype::initialize(JS::Realm& realm, JS::Object& object)
{
    [[maybe_unused]] auto& vm = realm.vm();
    [[maybe_unused]] u8 default_attributes = JS::Attribute::Enumerable | JS::Attribute::Configurable | JS::Attribute::Writable;

    object.set_prototype(GC::Ref { ensure_web_prototype<EventTargetPrototype>(realm, "EventTarget"_fly_string) });

    auto title_id = "title"_utf16_fly_string;
    auto native_title_getter = JS::NativeFunction::create(realm, title_getter, 0, title_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_title_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto title_attributes = default_attributes;

    // 5. Let desc be the PropertyDescriptor{[[Get]]: getter, [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: configurable}.

    // 7. Perform ! DefinePropertyOrThrow(target, id, desc).
    object.define_direct_accessor(title_id, native_title_getter, native_title_setter, title_attributes);

    // 8. FIXME: If attr’s type is an observable array type with type argument T, then set target’s backing observable array exotic object for attr to the result of creating an observable array exotic object in realm, given T, attr’s set an indexed value algorithm, and attr’s delete an indexed value algorithm.
    auto dir_id = "dir"_utf16_fly_string;
    auto native_dir_getter = JS::NativeFunction::create(realm, dir_getter, 0, dir_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_dir_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto dir_attributes = default_attributes;

    // 5. Let desc be the PropertyDescriptor{[[Get]]: getter, [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: configurable}.

    // 7. Perform ! DefinePropertyOrThrow(target, id, desc).
    object.define_direct_accessor(dir_id, native_dir_getter, native_dir_setter, dir_attributes);

    // 8. FIXME: If attr’s type is an observable array type with type argument T, then set target’s backing observable array exotic object for attr to the result of creating an observable array exotic object in realm, given T, attr’s set an indexed value algorithm, and attr’s delete an indexed value algorithm.
    auto lang_id = "lang"_utf16_fly_string;
    auto native_lang_getter = JS::NativeFunction::create(realm, lang_getter, 0, lang_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_lang_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto lang_attributes = default_attributes;

    // 5. Let desc be the PropertyDescriptor{[[Get]]: getter, [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: configurable}.

    // 7. Perform ! DefinePropertyOrThrow(target, id, desc).
    object.define_direct_accessor(lang_id, native_lang_getter, native_lang_setter, lang_attributes);

    // 8. FIXME: If attr’s type is an observable array type with type argument T, then set target’s backing observable array exotic object for attr to the result of creating an observable array exotic object in realm, given T, attr’s set an indexed value algorithm, and attr’s delete an indexed value algorithm.
    auto body_id = "body"_utf16_fly_string;
    auto native_body_getter = JS::NativeFunction::create(realm, body_getter, 0, body_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_body_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto body_attributes = default_attributes;

    // 5. Let desc be the PropertyDescriptor{[[Get]]: getter, [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: configurable}.

    // 7. Perform ! DefinePropertyOrThrow(target, id, desc).
    object.define_direct_accessor(body_id, native_body_getter, native_body_setter, body_attributes);

    // 8. FIXME: If attr’s type is an observable array type with type argument T, then set target’s backing observable array exotic object for attr to the result of creating an observable array exotic object in realm, given T, attr’s set an indexed value algorithm, and attr’s delete an indexed value algorithm.
    auto navigate_id = "navigate"_utf16_fly_string;
    auto native_navigate_getter = JS::NativeFunction::create(realm, navigate_getter, 0, navigate_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_navigate_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto navigate_attributes = default_attributes;

    // 5. Let desc be the PropertyDescriptor{[[Get]]: getter, [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: configurable}.

    // 7. Perform ! DefinePropertyOrThrow(target, id, desc).
    object.define_direct_accessor(navigate_id, native_navigate_getter, native_navigate_setter, navigate_attributes);

    // 8. FIXME: If attr’s type is an observable array type with type argument T, then set target’s backing observable array exotic object for attr to the result of creating an observable array exotic object in realm, given T, attr’s set an indexed value algorithm, and attr’s delete an indexed value algorithm.
    auto tag_id = "tag"_utf16_fly_string;
    auto native_tag_getter = JS::NativeFunction::create(realm, tag_getter, 0, tag_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_tag_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto tag_attributes = default_attributes;

    // 5. Let desc be the PropertyDescriptor{[[Get]]: getter, [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: configurable}.

    // 7. Perform ! DefinePropertyOrThrow(target, id, desc).
    object.define_direct_accessor(tag_id, native_tag_getter, native_tag_setter, tag_attributes);

    // 8. FIXME: If attr’s type is an observable array type with type argument T, then set target’s backing observable array exotic object for attr to the result of creating an observable array exotic object in realm, given T, attr’s set an indexed value algorithm, and attr’s delete an indexed value algorithm.
    auto image_id = "image"_utf16_fly_string;
    auto native_image_getter = JS::NativeFunction::create(realm, image_getter, 0, image_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_image_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto image_attributes = default_attributes;

    // 5. Let desc be the PropertyDescriptor{[[Get]]: getter, [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: configurable}.

    // 7. Perform ! DefinePropertyOrThrow(target, id, desc).
    object.define_direct_accessor(image_id, native_image_getter, native_image_setter, image_attributes);

    // 8. FIXME: If attr’s type is an observable array type with type argument T, then set target’s backing observable array exotic object for attr to the result of creating an observable array exotic object in realm, given T, attr’s set an indexed value algorithm, and attr’s delete an indexed value algorithm.
    auto icon_id = "icon"_utf16_fly_string;
    auto native_icon_getter = JS::NativeFunction::create(realm, icon_getter, 0, icon_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_icon_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto icon_attributes = default_attributes;

    // 5. Let desc be the PropertyDescriptor{[[Get]]: getter, [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: configurable}.

    // 7. Perform ! DefinePropertyOrThrow(target, id, desc).
    object.define_direct_accessor(icon_id, native_icon_getter, native_icon_setter, icon_attributes);

    // 8. FIXME: If attr’s type is an observable array type with type argument T, then set target’s backing observable array exotic object for attr to the result of creating an observable array exotic object in realm, given T, attr’s set an indexed value algorithm, and attr’s delete an indexed value algorithm.
    auto badge_id = "badge"_utf16_fly_string;
    auto native_badge_getter = JS::NativeFunction::create(realm, badge_getter, 0, badge_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_badge_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto badge_attributes = default_attributes;

    // 5. Let desc be the PropertyDescriptor{[[Get]]: getter, [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: configurable}.

    // 7. Perform ! DefinePropertyOrThrow(target, id, desc).
    object.define_direct_accessor(badge_id, native_badge_getter, native_badge_setter, badge_attributes);

    // 8. FIXME: If attr’s type is an observable array type with type argument T, then set target’s backing observable array exotic object for attr to the result of creating an observable array exotic object in realm, given T, attr’s set an indexed value algorithm, and attr’s delete an indexed value algorithm.
    auto timestamp_id = "timestamp"_utf16_fly_string;
    auto native_timestamp_getter = JS::NativeFunction::create(realm, timestamp_getter, 0, timestamp_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_timestamp_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto timestamp_attributes = default_attributes;

    // 5. Let desc be the PropertyDescriptor{[[Get]]: getter, [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: configurable}.

    // 7. Perform ! DefinePropertyOrThrow(target, id, desc).
    object.define_direct_accessor(timestamp_id, native_timestamp_getter, native_timestamp_setter, timestamp_attributes);

    // 8. FIXME: If attr’s type is an observable array type with type argument T, then set target’s backing observable array exotic object for attr to the result of creating an observable array exotic object in realm, given T, attr’s set an indexed value algorithm, and attr’s delete an indexed value algorithm.
    auto renotify_id = "renotify"_utf16_fly_string;
    auto native_renotify_getter = JS::NativeFunction::create(realm, renotify_getter, 0, renotify_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_renotify_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto renotify_attributes = default_attributes;

    // 5. Let desc be the PropertyDescriptor{[[Get]]: getter, [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: configurable}.

    // 7. Perform ! DefinePropertyOrThrow(target, id, desc).
    object.define_direct_accessor(renotify_id, native_renotify_getter, native_renotify_setter, renotify_attributes);

    // 8. FIXME: If attr’s type is an observable array type with type argument T, then set target’s backing observable array exotic object for attr to the result of creating an observable array exotic object in realm, given T, attr’s set an indexed value algorithm, and attr’s delete an indexed value algorithm.
    auto silent_id = "silent"_utf16_fly_string;
    auto native_silent_getter = JS::NativeFunction::create(realm, silent_getter, 0, silent_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_silent_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto silent_attributes = default_attributes;

    // 5. Let desc be the PropertyDescriptor{[[Get]]: getter, [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: configurable}.

    // 7. Perform ! DefinePropertyOrThrow(target, id, desc).
    object.define_direct_accessor(silent_id, native_silent_getter, native_silent_setter, silent_attributes);

    // 8. FIXME: If attr’s type is an observable array type with type argument T, then set target’s backing observable array exotic object for attr to the result of creating an observable array exotic object in realm, given T, attr’s set an indexed value algorithm, and attr’s delete an indexed value algorithm.
    auto require_interaction_id = "requireInteraction"_utf16_fly_string;
    auto native_require_interaction_getter = JS::NativeFunction::create(realm, require_interaction_getter, 0, require_interaction_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_require_interaction_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto require_interaction_attributes = default_attributes;

    // 5. Let desc be the PropertyDescriptor{[[Get]]: getter, [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: configurable}.

    // 7. Perform ! DefinePropertyOrThrow(target, id, desc).
    object.define_direct_accessor(require_interaction_id, native_require_interaction_getter, native_require_interaction_setter, require_interaction_attributes);

    // 8. FIXME: If attr’s type is an observable array type with type argument T, then set target’s backing observable array exotic object for attr to the result of creating an observable array exotic object in realm, given T, attr’s set an indexed value algorithm, and attr’s delete an indexed value algorithm.
    auto data_id = "data"_utf16_fly_string;
    auto native_data_getter = JS::NativeFunction::create(realm, data_getter, 0, data_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_data_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto data_attributes = default_attributes;

    // 5. Let desc be the PropertyDescriptor{[[Get]]: getter, [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: configurable}.

    // 7. Perform ! DefinePropertyOrThrow(target, id, desc).
    object.define_direct_accessor(data_id, native_data_getter, native_data_setter, data_attributes);

    // 8. FIXME: If attr’s type is an observable array type with type argument T, then set target’s backing observable array exotic object for attr to the result of creating an observable array exotic object in realm, given T, attr’s set an indexed value algorithm, and attr’s delete an indexed value algorithm.
    auto actions_id = "actions"_utf16_fly_string;
    auto native_actions_getter = JS::NativeFunction::create(realm, actions_getter, 0, actions_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_actions_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto actions_attributes = default_attributes;

    // 5. Let desc be the PropertyDescriptor{[[Get]]: getter, [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: configurable}.

    // 7. Perform ! DefinePropertyOrThrow(target, id, desc).
    object.define_direct_accessor(actions_id, native_actions_getter, native_actions_setter, actions_attributes);

    // 8. FIXME: If attr’s type is an observable array type with type argument T, then set target’s backing observable array exotic object for attr to the result of creating an observable array exotic object in realm, given T, attr’s set an indexed value algorithm, and attr’s delete an indexed value algorithm.
    object.define_direct_property("close"_utf16_fly_string, JS::js_undefined(), default_attributes | JS::Attribute::Unimplemented);

    object.define_direct_property(vm.well_known_symbol_to_string_tag(), JS::PrimitiveString::create(vm, "Notification"_utf16), JS::Attribute::Configurable);
}

void NotificationPrototype::define_unforgeable_attributes(JS::Realm& realm, [[maybe_unused]] JS::Object& object)
{
    [[maybe_unused]] auto& vm = realm.vm();
    [[maybe_unused]] u8 default_attributes = JS::Attribute::Enumerable;
}

[[maybe_unused]] static JS::ThrowCompletionOr<NotificationsAPI::Notification*> impl_from(JS::VM& vm, JS::Value js_value)
{

    if (auto impl = js_value.as_if<NotificationsAPI::Notification>())
        return impl.ptr();
    return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "Notification");
}

[[maybe_unused]] static JS::ThrowCompletionOr<NotificationsAPI::Notification*> impl_from(JS::VM& vm)
{
    auto this_value = vm.this_value();
    if (this_value.is_nullish())
        this_value = &vm.current_realm()->global_object();
    return impl_from(vm, this_value);
}

JS_DEFINE_NATIVE_FUNCTION(NotificationConstructor::max_actions_getter)
{
    WebIDL::log_trace(vm, "NotificationConstructor::max_actions_getter");

    // Let R be the result of running the getter steps of attribute.
    auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return NotificationsAPI::Notification::max_actions(vm); }));

    // Return the result of converting R to a JavaScript value of the type attribute is declared as.
    return JS::Value(static_cast<WebIDL::UnsignedLong>(R));
}

JS_DEFINE_NATIVE_FUNCTION(NotificationPrototype::title_getter)
{
    WebIDL::log_trace(vm, "NotificationPrototype::title_getter");
    [[maybe_unused]] auto& realm = *vm.current_realm();

    auto* idl_object = TRY(impl_from(vm));


    auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->title(); }));

    return WebIDL::primitive_string_from_string(vm, R);
}

JS_DEFINE_NATIVE_FUNCTION(NotificationPrototype::dir_getter)
{
    WebIDL::log_trace(vm, "NotificationPrototype::dir_getter");
    [[maybe_unused]] auto& realm = *vm.current_realm();

    auto* idl_object = TRY(impl_from(vm));


    auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->dir(); }));

    return JS::PrimitiveString::create(vm, idl_enum_to_string(R));
}

JS_DEFINE_NATIVE_FUNCTION(NotificationPrototype::lang_getter)
{
    WebIDL::log_trace(vm, "NotificationPrototype::lang_getter");
    [[maybe_unused]] auto& realm = *vm.current_realm();

    auto* idl_object = TRY(impl_from(vm));


    auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->lang(); }));

    return WebIDL::primitive_string_from_string(vm, R);
}

JS_DEFINE_NATIVE_FUNCTION(NotificationPrototype::body_getter)
{
    WebIDL::log_trace(vm, "NotificationPrototype::body_getter");
    [[maybe_unused]] auto& realm = *vm.current_realm();

    auto* idl_object = TRY(impl_from(vm));


    auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->body(); }));

    return WebIDL::primitive_string_from_string(vm, R);
}

JS_DEFINE_NATIVE_FUNCTION(NotificationPrototype::navigate_getter)
{
    WebIDL::log_trace(vm, "NotificationPrototype::navigate_getter");
    [[maybe_unused]] auto& realm = *vm.current_realm();

    auto* idl_object = TRY(impl_from(vm));


    auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->navigate(); }));

    return WebIDL::primitive_string_from_string(vm, R);
}

JS_DEFINE_NATIVE_FUNCTION(NotificationPrototype::tag_getter)
{
    WebIDL::log_trace(vm, "NotificationPrototype::tag_getter");
    [[maybe_unused]] auto& realm = *vm.current_realm();

    auto* idl_object = TRY(impl_from(vm));


    auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->tag(); }));

    return WebIDL::primitive_string_from_string(vm, R);
}

JS_DEFINE_NATIVE_FUNCTION(NotificationPrototype::image_getter)
{
    WebIDL::log_trace(vm, "NotificationPrototype::image_getter");
    [[maybe_unused]] auto& realm = *vm.current_realm();

    auto* idl_object = TRY(impl_from(vm));


    auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->image(); }));

    return WebIDL::primitive_string_from_string(vm, R);
}

JS_DEFINE_NATIVE_FUNCTION(NotificationPrototype::icon_getter)
{
    WebIDL::log_trace(vm, "NotificationPrototype::icon_getter");
    [[maybe_unused]] auto& realm = *vm.current_realm();

    auto* idl_object = TRY(impl_from(vm));


    auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->icon(); }));

    return WebIDL::primitive_string_from_string(vm, R);
}

JS_DEFINE_NATIVE_FUNCTION(NotificationPrototype::badge_getter)
{
    WebIDL::log_trace(vm, "NotificationPrototype::badge_getter");
    [[maybe_unused]] auto& realm = *vm.current_realm();

    auto* idl_object = TRY(impl_from(vm));


    auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->badge(); }));

    return WebIDL::primitive_string_from_string(vm, R);
}

JS_DEFINE_NATIVE_FUNCTION(NotificationPrototype::timestamp_getter)
{
    WebIDL::log_trace(vm, "NotificationPrototype::timestamp_getter");
    [[maybe_unused]] auto& realm = *vm.current_realm();

    auto* idl_object = TRY(impl_from(vm));


    auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->timestamp(); }));

    return JS::Value(static_cast<double>(R));
}

JS_DEFINE_NATIVE_FUNCTION(NotificationPrototype::renotify_getter)
{
    WebIDL::log_trace(vm, "NotificationPrototype::renotify_getter");
    [[maybe_unused]] auto& realm = *vm.current_realm();

    auto* idl_object = TRY(impl_from(vm));


    auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->renotify(); }));

    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(NotificationPrototype::silent_getter)
{
    WebIDL::log_trace(vm, "NotificationPrototype::silent_getter");
    [[maybe_unused]] auto& realm = *vm.current_realm();

    auto* idl_object = TRY(impl_from(vm));


    auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->silent(); }));

    return [&]() -> JS::Value {
        // 1. If the IDL nullable type T? value is null, then the JavaScript value is null.
        if (!R.has_value())
            return JS::js_null();

        // 2. Otherwise, the JavaScript value is the result of converting the IDL nullable type value to the inner IDL type T.
        return JS::Value(JS::Value(R.value()));
    }();
}

JS_DEFINE_NATIVE_FUNCTION(NotificationPrototype::require_interaction_getter)
{
    WebIDL::log_trace(vm, "NotificationPrototype::require_interaction_getter");
    [[maybe_unused]] auto& realm = *vm.current_realm();

    auto* idl_object = TRY(impl_from(vm));


    auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->require_interaction(); }));

    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(NotificationPrototype::data_getter)
{
    WebIDL::log_trace(vm, "NotificationPrototype::data_getter");
    [[maybe_unused]] auto& realm = *vm.current_realm();

    auto* idl_object = TRY(impl_from(vm));


    auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->data(); }));

    return R;
}

JS_DEFINE_NATIVE_FUNCTION(NotificationPrototype::actions_getter)
{
    WebIDL::log_trace(vm, "NotificationPrototype::actions_getter");
    [[maybe_unused]] auto& realm = *vm.current_realm();

    auto* idl_object = TRY(impl_from(vm));


    auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->actions(); }));

    return [&]() -> JS::Value {
        // An IDL sequence<T> value S is converted to a JavaScript value as follows:
        // 1. Let n be the length of S.
        auto sequence_length = R.size();

        // 2. Let A be a new Array object created as if by the expression [].
        auto sequence_array = MUST(JS::Array::create(realm, sequence_length));

        // 3. Initialize i to be 0.
        // 4. While i < n:
        for (size_t sequence_index = 0; sequence_index < sequence_length; ++sequence_index) {
            // 1. Let V be the value in S at index i.
            auto& sequence_element = R.at(sequence_index);

            // 2. Let E be the result of converting V to a JavaScript value.
            JS::Value js_sequence_element = [&]() -> JS::Value {
        // 1. Let O be OrdinaryObjectCreate(%Object.prototype%).
        auto dictionary_object = JS::Object::create(realm, realm.intrinsics().object_prototype());

        // 1. Let key be the identifier of member.
        // 2. If V[key] exists, then:

        // 1. Let idlValue be V[key].
        // 2. Let value be the result of converting idlValue to a JavaScript value.
        // 3. Perform ! CreateDataPropertyOrThrow(O, key, value).
        MUST(dictionary_object->create_data_property("action"_utf16_fly_string, WebIDL::primitive_string_from_string(vm, sequence_element.action)));

        // 1. Let key be the identifier of member.
        // 2. If V[key] exists, then:
        if (sequence_element.icon.has_value()) {

        // 1. Let idlValue be V[key].
        // 2. Let value be the result of converting idlValue to a JavaScript value.
        // 3. Perform ! CreateDataPropertyOrThrow(O, key, value).
        MUST(dictionary_object->create_data_property("icon"_utf16_fly_string, WebIDL::primitive_string_from_string(vm, sequence_element.icon.value())));
        }

        // 1. Let key be the identifier of member.
        // 2. If V[key] exists, then:
        if (sequence_element.navigate.has_value()) {

        // 1. Let idlValue be V[key].
        // 2. Let value be the result of converting idlValue to a JavaScript value.
        // 3. Perform ! CreateDataPropertyOrThrow(O, key, value).
        MUST(dictionary_object->create_data_property("navigate"_utf16_fly_string, WebIDL::primitive_string_from_string(vm, sequence_element.navigate.value())));
        }

        // 1. Let key be the identifier of member.
        // 2. If V[key] exists, then:

        // 1. Let idlValue be V[key].
        // 2. Let value be the result of converting idlValue to a JavaScript value.
        // 3. Perform ! CreateDataPropertyOrThrow(O, key, value).
        MUST(dictionary_object->create_data_property("title"_utf16_fly_string, WebIDL::primitive_string_from_string(vm, sequence_element.title)));

        // 4. Return O.
        return dictionary_object;
    }();

            // 3. Let P be the result of calling ! ToString(i).
            // 4. Perform ! CreateDataPropertyOrThrow(A, P, E).
            MUST(sequence_array->create_data_property(JS::PropertyKey { sequence_index }, js_sequence_element));

            // 5. Set i to i + 1.
        }

        MUST(sequence_array->set_integrity_level(JS::Object::IntegrityLevel::Frozen));

        // 5. Return A.
        return sequence_array;
    }();
}

// https://webidl.spec.whatwg.org/#idl-enumeration
JS::ThrowCompletionOr<NotificationPermission> convert_to_idl_value_for_notification_permission(JS::VM& vm, JS::Value value)
{
    // 1. Let S be the result of calling ? ToString(V).
    auto value_as_string = TRY(value.to_utf16_string(vm));

    // 2. If S is not one of E’s enumeration values, then throw a TypeError.
    // 3. Return the enumeration value of type E that is equal to S.
    if (value_as_string == "default"sv)
        return NotificationPermission::Default;
    if (value_as_string == "denied"sv)
        return NotificationPermission::Denied;
    if (value_as_string == "granted"sv)
        return NotificationPermission::Granted;
    return vm.throw_completion<JS::TypeError>(JS::ErrorType::InvalidEnumerationValue, value_as_string, "NotificationPermission");
}

// https://webidl.spec.whatwg.org/#idl-enumeration
Utf16String idl_enum_to_string(NotificationPermission value)
{
    // The result of converting an IDL enumeration type value to a JavaScript value is the String value that represents the same sequence of code units as the enumeration value.
    switch (value) {
    case NotificationPermission::Default:
        return "default"_utf16;
    case NotificationPermission::Denied:
        return "denied"_utf16;
    case NotificationPermission::Granted:
        return "granted"_utf16;
    }
    VERIFY_NOT_REACHED();
}

// https://webidl.spec.whatwg.org/#idl-enumeration
JS::ThrowCompletionOr<NotificationDirection> convert_to_idl_value_for_notification_direction(JS::VM& vm, JS::Value value)
{
    // 1. Let S be the result of calling ? ToString(V).
    auto value_as_string = TRY(value.to_utf16_string(vm));

    // 2. If S is not one of E’s enumeration values, then throw a TypeError.
    // 3. Return the enumeration value of type E that is equal to S.
    if (value_as_string == "auto"sv)
        return NotificationDirection::Auto;
    if (value_as_string == "ltr"sv)
        return NotificationDirection::Ltr;
    if (value_as_string == "rtl"sv)
        return NotificationDirection::Rtl;
    return vm.throw_completion<JS::TypeError>(JS::ErrorType::InvalidEnumerationValue, value_as_string, "NotificationDirection");
}

// https://webidl.spec.whatwg.org/#idl-enumeration
Utf16String idl_enum_to_string(NotificationDirection value)
{
    // The result of converting an IDL enumeration type value to a JavaScript value is the String value that represents the same sequence of code units as the enumeration value.
    switch (value) {
    case NotificationDirection::Auto:
        return "auto"_utf16;
    case NotificationDirection::Ltr:
        return "ltr"_utf16;
    case NotificationDirection::Rtl:
        return "rtl"_utf16;
    }
    VERIFY_NOT_REACHED();
}

// https://webidl.spec.whatwg.org/#es-dictionary
JS::ThrowCompletionOr<NotificationOptions> convert_to_idl_value_for_notification_options(JS::VM& vm, JS::Value js_dict)
{
    // 1. If jsDict is not an Object and jsDict is neither undefined nor null, then throw a TypeError.
    if (!js_dict.is_object() && !js_dict.is_nullish())
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "NotificationOptions");

    // 2. Let idlDict be an empty ordered map, representing a dictionary of type D.
    // 3. Let dictionaries be a list consisting of D and all of D’s inherited dictionaries, in order from least to most derived.
    // 4. For each dictionary dictionary in dictionaries, in order:
    // NB: We defer construction until the return initializer because some members may not be default-constructible. Inherited dictionaries are represented by the generated C++ struct inheritance.

    // 5. Return idlDict.
    return NotificationOptions {
        .actions = TRY([&]() -> JS::ThrowCompletionOr<Vector<NotificationAction>> {
            // 1. Let key be the identifier of member.
            // 2. If jsDict is either undefined or null, then:
            //     1. Let jsMemberValue be undefined.
            // 3. Otherwise,
            //     1. Let jsMemberValue be ? Get(jsDict, key).
            auto js_member_value = JS::js_undefined();
            if (js_dict.is_object())
                js_member_value = TRY(js_dict.as_object().get("actions"_utf16_fly_string));

            // 4. If jsMemberValue is not undefined, then:
            if (!js_member_value.is_undefined()) {
                // 1. Let idlMemberValue be the result of converting jsMemberValue to an IDL value whose type is the type member is declared to be of.
                auto idl_member_value = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<Vector<NotificationAction>> {
        if (!js_member_value.is_object())
            return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObject, js_member_value);

        auto method = TRY(js_member_value.get_method(vm, vm.well_known_symbol_iterator()));
        if (!method)
            return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotIterable, js_member_value);

        return TRY([&]() -> JS::ThrowCompletionOr<Vector<NotificationAction>> {
        // To create an IDL value of type sequence<T> given an iterable iterable and an iterator getter method, perform the following steps:
        // 1. Let iteratorRecord be ? GetIteratorFromMethod(iterable, method).
        auto iterator = TRY(JS::get_iterator_from_method(vm, js_member_value, *method));

        Vector<NotificationAction> sequence;

        // 2. Initialize i to be 0.
        // 3. Repeat
        for (;;) {
            // 1. Let next be ? IteratorStepValue(iteratorRecord).
            auto next = TRY(JS::iterator_step_value(vm, iterator));

            // 2. If next is done, then return an IDL sequence value of type sequence<T> of length i, where the value of the element at index j is Sj.
            if (!next.has_value())
                break;

            // 3. Initialize Si to the result of converting next to an IDL value of type T.
            auto next_value = next.release_value();
            auto sequence_item = TRY(throw_dom_exception_if_needed(vm, [&] { return convert_to_idl_value_for_notification_action(vm, next_value); }));

            // 4. Set i to i + 1.
            sequence.append(sequence_item);
        }

        return sequence;
    }());
    }(); }));

                // 2. Set idlDict[key] to idlMemberValue.
                return idl_member_value;
            }
            // 5. Otherwise, if jsMemberValue is undefined but member has a default value, then:
            // 1. Let idlMemberValue be the result of converting member's default value to an IDL value whose type is the type member is declared to be of.
            auto idl_member_value = Vector<NotificationAction> {};

            // 2. Set idlDict[key] to idlMemberValue.
            return idl_member_value;
        }()),
        .badge = TRY([&]() -> JS::ThrowCompletionOr<Optional<String>> {
            // 1. Let key be the identifier of member.
            // 2. If jsDict is either undefined or null, then:
            //     1. Let jsMemberValue be undefined.
            // 3. Otherwise,
            //     1. Let jsMemberValue be ? Get(jsDict, key).
            auto js_member_value = JS::js_undefined();
            if (js_dict.is_object())
                js_member_value = TRY(js_dict.as_object().get("badge"_utf16_fly_string));

            // 4. If jsMemberValue is not undefined, then:
            if (!js_member_value.is_undefined()) {
                // 1. Let idlMemberValue be the result of converting jsMemberValue to an IDL value whose type is the type member is declared to be of.
                auto idl_member_value = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<String> {
        return TRY(WebIDL::to_usv_string(vm, js_member_value));
    }(); }));

                // 2. Set idlDict[key] to idlMemberValue.
                return idl_member_value;
            }
            // 7. Otherwise, jsMemberValue is undefined and the member is optional.
            return OptionalNone {};
        }()),
        .body = TRY([&]() -> JS::ThrowCompletionOr<String> {
            // 1. Let key be the identifier of member.
            // 2. If jsDict is either undefined or null, then:
            //     1. Let jsMemberValue be undefined.
            // 3. Otherwise,
            //     1. Let jsMemberValue be ? Get(jsDict, key).
            auto js_member_value = JS::js_undefined();
            if (js_dict.is_object())
                js_member_value = TRY(js_dict.as_object().get("body"_utf16_fly_string));

            // 4. If jsMemberValue is not undefined, then:
            if (!js_member_value.is_undefined()) {
                // 1. Let idlMemberValue be the result of converting jsMemberValue to an IDL value whose type is the type member is declared to be of.
                auto idl_member_value = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<String> {
        return TRY(WebIDL::to_string(vm, js_member_value));
    }(); }));

                // 2. Set idlDict[key] to idlMemberValue.
                return idl_member_value;
            }
            // 5. Otherwise, if jsMemberValue is undefined but member has a default value, then:
            // 1. Let idlMemberValue be the result of converting member's default value to an IDL value whose type is the type member is declared to be of.
            auto idl_member_value = String {};

            // 2. Set idlDict[key] to idlMemberValue.
            return idl_member_value;
        }()),
        .data = TRY([&]() -> JS::ThrowCompletionOr<JS::Value> {
            // 1. Let key be the identifier of member.
            // 2. If jsDict is either undefined or null, then:
            //     1. Let jsMemberValue be undefined.
            // 3. Otherwise,
            //     1. Let jsMemberValue be ? Get(jsDict, key).
            auto js_member_value = JS::js_undefined();
            if (js_dict.is_object())
                js_member_value = TRY(js_dict.as_object().get("data"_utf16_fly_string));

            // 4. If jsMemberValue is not undefined, then:
            if (!js_member_value.is_undefined()) {
                // 1. Let idlMemberValue be the result of converting jsMemberValue to an IDL value whose type is the type member is declared to be of.
                auto idl_member_value = TRY(throw_dom_exception_if_needed(vm, [&] { return js_member_value; }));

                // 2. Set idlDict[key] to idlMemberValue.
                return idl_member_value;
            }
            // 5. Otherwise, if jsMemberValue is undefined but member has a default value, then:
            // 1. Let idlMemberValue be the result of converting member's default value to an IDL value whose type is the type member is declared to be of.
            auto idl_member_value = JS::js_null();

            // 2. Set idlDict[key] to idlMemberValue.
            return idl_member_value;
        }()),
        .dir = TRY([&]() -> JS::ThrowCompletionOr<NotificationDirection> {
            // 1. Let key be the identifier of member.
            // 2. If jsDict is either undefined or null, then:
            //     1. Let jsMemberValue be undefined.
            // 3. Otherwise,
            //     1. Let jsMemberValue be ? Get(jsDict, key).
            auto js_member_value = JS::js_undefined();
            if (js_dict.is_object())
                js_member_value = TRY(js_dict.as_object().get("dir"_utf16_fly_string));

            // 4. If jsMemberValue is not undefined, then:
            if (!js_member_value.is_undefined()) {
                // 1. Let idlMemberValue be the result of converting jsMemberValue to an IDL value whose type is the type member is declared to be of.
                auto idl_member_value = TRY(throw_dom_exception_if_needed(vm, [&] { return convert_to_idl_value_for_notification_direction(vm, js_member_value); }));

                // 2. Set idlDict[key] to idlMemberValue.
                return idl_member_value;
            }
            // 5. Otherwise, if jsMemberValue is undefined but member has a default value, then:
            // 1. Let idlMemberValue be the result of converting member's default value to an IDL value whose type is the type member is declared to be of.
            auto idl_member_value = NotificationDirection::Auto;

            // 2. Set idlDict[key] to idlMemberValue.
            return idl_member_value;
        }()),
        .icon = TRY([&]() -> JS::ThrowCompletionOr<Optional<String>> {
            // 1. Let key be the identifier of member.
            // 2. If jsDict is either undefined or null, then:
            //     1. Let jsMemberValue be undefined.
            // 3. Otherwise,
            //     1. Let jsMemberValue be ? Get(jsDict, key).
            auto js_member_value = JS::js_undefined();
            if (js_dict.is_object())
                js_member_value = TRY(js_dict.as_object().get("icon"_utf16_fly_string));

            // 4. If jsMemberValue is not undefined, then:
            if (!js_member_value.is_undefined()) {
                // 1. Let idlMemberValue be the result of converting jsMemberValue to an IDL value whose type is the type member is declared to be of.
                auto idl_member_value = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<String> {
        return TRY(WebIDL::to_usv_string(vm, js_member_value));
    }(); }));

                // 2. Set idlDict[key] to idlMemberValue.
                return idl_member_value;
            }
            // 7. Otherwise, jsMemberValue is undefined and the member is optional.
            return OptionalNone {};
        }()),
        .image = TRY([&]() -> JS::ThrowCompletionOr<Optional<String>> {
            // 1. Let key be the identifier of member.
            // 2. If jsDict is either undefined or null, then:
            //     1. Let jsMemberValue be undefined.
            // 3. Otherwise,
            //     1. Let jsMemberValue be ? Get(jsDict, key).
            auto js_member_value = JS::js_undefined();
            if (js_dict.is_object())
                js_member_value = TRY(js_dict.as_object().get("image"_utf16_fly_string));

            // 4. If jsMemberValue is not undefined, then:
            if (!js_member_value.is_undefined()) {
                // 1. Let idlMemberValue be the result of converting jsMemberValue to an IDL value whose type is the type member is declared to be of.
                auto idl_member_value = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<String> {
        return TRY(WebIDL::to_usv_string(vm, js_member_value));
    }(); }));

                // 2. Set idlDict[key] to idlMemberValue.
                return idl_member_value;
            }
            // 7. Otherwise, jsMemberValue is undefined and the member is optional.
            return OptionalNone {};
        }()),
        .lang = TRY([&]() -> JS::ThrowCompletionOr<String> {
            // 1. Let key be the identifier of member.
            // 2. If jsDict is either undefined or null, then:
            //     1. Let jsMemberValue be undefined.
            // 3. Otherwise,
            //     1. Let jsMemberValue be ? Get(jsDict, key).
            auto js_member_value = JS::js_undefined();
            if (js_dict.is_object())
                js_member_value = TRY(js_dict.as_object().get("lang"_utf16_fly_string));

            // 4. If jsMemberValue is not undefined, then:
            if (!js_member_value.is_undefined()) {
                // 1. Let idlMemberValue be the result of converting jsMemberValue to an IDL value whose type is the type member is declared to be of.
                auto idl_member_value = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<String> {
        return TRY(WebIDL::to_string(vm, js_member_value));
    }(); }));

                // 2. Set idlDict[key] to idlMemberValue.
                return idl_member_value;
            }
            // 5. Otherwise, if jsMemberValue is undefined but member has a default value, then:
            // 1. Let idlMemberValue be the result of converting member's default value to an IDL value whose type is the type member is declared to be of.
            auto idl_member_value = String {};

            // 2. Set idlDict[key] to idlMemberValue.
            return idl_member_value;
        }()),
        .navigate = TRY([&]() -> JS::ThrowCompletionOr<Optional<String>> {
            // 1. Let key be the identifier of member.
            // 2. If jsDict is either undefined or null, then:
            //     1. Let jsMemberValue be undefined.
            // 3. Otherwise,
            //     1. Let jsMemberValue be ? Get(jsDict, key).
            auto js_member_value = JS::js_undefined();
            if (js_dict.is_object())
                js_member_value = TRY(js_dict.as_object().get("navigate"_utf16_fly_string));

            // 4. If jsMemberValue is not undefined, then:
            if (!js_member_value.is_undefined()) {
                // 1. Let idlMemberValue be the result of converting jsMemberValue to an IDL value whose type is the type member is declared to be of.
                auto idl_member_value = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<String> {
        return TRY(WebIDL::to_usv_string(vm, js_member_value));
    }(); }));

                // 2. Set idlDict[key] to idlMemberValue.
                return idl_member_value;
            }
            // 7. Otherwise, jsMemberValue is undefined and the member is optional.
            return OptionalNone {};
        }()),
        .renotify = TRY([&]() -> JS::ThrowCompletionOr<bool> {
            // 1. Let key be the identifier of member.
            // 2. If jsDict is either undefined or null, then:
            //     1. Let jsMemberValue be undefined.
            // 3. Otherwise,
            //     1. Let jsMemberValue be ? Get(jsDict, key).
            auto js_member_value = JS::js_undefined();
            if (js_dict.is_object())
                js_member_value = TRY(js_dict.as_object().get("renotify"_utf16_fly_string));

            // 4. If jsMemberValue is not undefined, then:
            if (!js_member_value.is_undefined()) {
                // 1. Let idlMemberValue be the result of converting jsMemberValue to an IDL value whose type is the type member is declared to be of.
                auto idl_member_value = TRY(throw_dom_exception_if_needed(vm, [&] { return js_member_value.to_boolean(); }));

                // 2. Set idlDict[key] to idlMemberValue.
                return idl_member_value;
            }
            // 5. Otherwise, if jsMemberValue is undefined but member has a default value, then:
            // 1. Let idlMemberValue be the result of converting member's default value to an IDL value whose type is the type member is declared to be of.
            auto idl_member_value = false;

            // 2. Set idlDict[key] to idlMemberValue.
            return idl_member_value;
        }()),
        .require_interaction = TRY([&]() -> JS::ThrowCompletionOr<bool> {
            // 1. Let key be the identifier of member.
            // 2. If jsDict is either undefined or null, then:
            //     1. Let jsMemberValue be undefined.
            // 3. Otherwise,
            //     1. Let jsMemberValue be ? Get(jsDict, key).
            auto js_member_value = JS::js_undefined();
            if (js_dict.is_object())
                js_member_value = TRY(js_dict.as_object().get("requireInteraction"_utf16_fly_string));

            // 4. If jsMemberValue is not undefined, then:
            if (!js_member_value.is_undefined()) {
                // 1. Let idlMemberValue be the result of converting jsMemberValue to an IDL value whose type is the type member is declared to be of.
                auto idl_member_value = TRY(throw_dom_exception_if_needed(vm, [&] { return js_member_value.to_boolean(); }));

                // 2. Set idlDict[key] to idlMemberValue.
                return idl_member_value;
            }
            // 5. Otherwise, if jsMemberValue is undefined but member has a default value, then:
            // 1. Let idlMemberValue be the result of converting member's default value to an IDL value whose type is the type member is declared to be of.
            auto idl_member_value = false;

            // 2. Set idlDict[key] to idlMemberValue.
            return idl_member_value;
        }()),
        .silent = TRY([&]() -> JS::ThrowCompletionOr<Optional<bool>> {
            // 1. Let key be the identifier of member.
            // 2. If jsDict is either undefined or null, then:
            //     1. Let jsMemberValue be undefined.
            // 3. Otherwise,
            //     1. Let jsMemberValue be ? Get(jsDict, key).
            auto js_member_value = JS::js_undefined();
            if (js_dict.is_object())
                js_member_value = TRY(js_dict.as_object().get("silent"_utf16_fly_string));

            // 4. If jsMemberValue is not undefined, then:
            if (!js_member_value.is_undefined()) {
                // 1. Let idlMemberValue be the result of converting jsMemberValue to an IDL value whose type is the type member is declared to be of.
                auto idl_member_value = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<Optional<bool>> {
        Optional<bool> value;

        if (!js_member_value.is_nullish()) {

            value = js_member_value.to_boolean();
        }
        return value;
    }(); }));

                // 2. Set idlDict[key] to idlMemberValue.
                return idl_member_value;
            }
            // 5. Otherwise, if jsMemberValue is undefined but member has a default value, then:
            // 1. Let idlMemberValue be the result of converting member's default value to an IDL value whose type is the type member is declared to be of.
            auto idl_member_value = OptionalNone {};

            // 2. Set idlDict[key] to idlMemberValue.
            return idl_member_value;
        }()),
        .tag = TRY([&]() -> JS::ThrowCompletionOr<String> {
            // 1. Let key be the identifier of member.
            // 2. If jsDict is either undefined or null, then:
            //     1. Let jsMemberValue be undefined.
            // 3. Otherwise,
            //     1. Let jsMemberValue be ? Get(jsDict, key).
            auto js_member_value = JS::js_undefined();
            if (js_dict.is_object())
                js_member_value = TRY(js_dict.as_object().get("tag"_utf16_fly_string));

            // 4. If jsMemberValue is not undefined, then:
            if (!js_member_value.is_undefined()) {
                // 1. Let idlMemberValue be the result of converting jsMemberValue to an IDL value whose type is the type member is declared to be of.
                auto idl_member_value = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<String> {
        return TRY(WebIDL::to_string(vm, js_member_value));
    }(); }));

                // 2. Set idlDict[key] to idlMemberValue.
                return idl_member_value;
            }
            // 5. Otherwise, if jsMemberValue is undefined but member has a default value, then:
            // 1. Let idlMemberValue be the result of converting member's default value to an IDL value whose type is the type member is declared to be of.
            auto idl_member_value = String {};

            // 2. Set idlDict[key] to idlMemberValue.
            return idl_member_value;
        }()),
        .timestamp = TRY([&]() -> JS::ThrowCompletionOr<Optional<WebIDL::UnsignedLongLong>> {
            // 1. Let key be the identifier of member.
            // 2. If jsDict is either undefined or null, then:
            //     1. Let jsMemberValue be undefined.
            // 3. Otherwise,
            //     1. Let jsMemberValue be ? Get(jsDict, key).
            auto js_member_value = JS::js_undefined();
            if (js_dict.is_object())
                js_member_value = TRY(js_dict.as_object().get("timestamp"_utf16_fly_string));

            // 4. If jsMemberValue is not undefined, then:
            if (!js_member_value.is_undefined()) {
                // 1. Let idlMemberValue be the result of converting jsMemberValue to an IDL value whose type is the type member is declared to be of.
                auto idl_member_value = TRY(throw_dom_exception_if_needed(vm, [&] { return WebIDL::convert_to_int<WebIDL::UnsignedLongLong>(vm, js_member_value, WebIDL::EnforceRange::No, WebIDL::Clamp::No); }));

                // 2. Set idlDict[key] to idlMemberValue.
                return idl_member_value;
            }
            // 7. Otherwise, jsMemberValue is undefined and the member is optional.
            return OptionalNone {};
        }()),
    };
}

// https://webidl.spec.whatwg.org/#es-dictionary
JS::ThrowCompletionOr<NotificationAction> convert_to_idl_value_for_notification_action(JS::VM& vm, JS::Value js_dict)
{
    // 1. If jsDict is not an Object and jsDict is neither undefined nor null, then throw a TypeError.
    if (!js_dict.is_object() && !js_dict.is_nullish())
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "NotificationAction");

    // 2. Let idlDict be an empty ordered map, representing a dictionary of type D.
    // 3. Let dictionaries be a list consisting of D and all of D’s inherited dictionaries, in order from least to most derived.
    // 4. For each dictionary dictionary in dictionaries, in order:
    // NB: We defer construction until the return initializer because some members may not be default-constructible. Inherited dictionaries are represented by the generated C++ struct inheritance.

    // 5. Return idlDict.
    return NotificationAction {
        .action = TRY([&]() -> JS::ThrowCompletionOr<String> {
            // 1. Let key be the identifier of member.
            // 2. If jsDict is either undefined or null, then:
            //     1. Let jsMemberValue be undefined.
            // 3. Otherwise,
            //     1. Let jsMemberValue be ? Get(jsDict, key).
            auto js_member_value = JS::js_undefined();
            if (js_dict.is_object())
                js_member_value = TRY(js_dict.as_object().get("action"_utf16_fly_string));

            // 4. If jsMemberValue is not undefined, then:
            if (!js_member_value.is_undefined()) {
                // 1. Let idlMemberValue be the result of converting jsMemberValue to an IDL value whose type is the type member is declared to be of.
                auto idl_member_value = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<String> {
        return TRY(WebIDL::to_string(vm, js_member_value));
    }(); }));

                // 2. Set idlDict[key] to idlMemberValue.
                return idl_member_value;
            }
            // 6. Otherwise, if jsMemberValue is undefined and member is required, then throw a TypeError.
            return vm.throw_completion<JS::TypeError>(JS::ErrorType::MissingRequiredProperty, "action");
        }()),
        .icon = TRY([&]() -> JS::ThrowCompletionOr<Optional<String>> {
            // 1. Let key be the identifier of member.
            // 2. If jsDict is either undefined or null, then:
            //     1. Let jsMemberValue be undefined.
            // 3. Otherwise,
            //     1. Let jsMemberValue be ? Get(jsDict, key).
            auto js_member_value = JS::js_undefined();
            if (js_dict.is_object())
                js_member_value = TRY(js_dict.as_object().get("icon"_utf16_fly_string));

            // 4. If jsMemberValue is not undefined, then:
            if (!js_member_value.is_undefined()) {
                // 1. Let idlMemberValue be the result of converting jsMemberValue to an IDL value whose type is the type member is declared to be of.
                auto idl_member_value = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<String> {
        return TRY(WebIDL::to_usv_string(vm, js_member_value));
    }(); }));

                // 2. Set idlDict[key] to idlMemberValue.
                return idl_member_value;
            }
            // 7. Otherwise, jsMemberValue is undefined and the member is optional.
            return OptionalNone {};
        }()),
        .navigate = TRY([&]() -> JS::ThrowCompletionOr<Optional<String>> {
            // 1. Let key be the identifier of member.
            // 2. If jsDict is either undefined or null, then:
            //     1. Let jsMemberValue be undefined.
            // 3. Otherwise,
            //     1. Let jsMemberValue be ? Get(jsDict, key).
            auto js_member_value = JS::js_undefined();
            if (js_dict.is_object())
                js_member_value = TRY(js_dict.as_object().get("navigate"_utf16_fly_string));

            // 4. If jsMemberValue is not undefined, then:
            if (!js_member_value.is_undefined()) {
                // 1. Let idlMemberValue be the result of converting jsMemberValue to an IDL value whose type is the type member is declared to be of.
                auto idl_member_value = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<String> {
        return TRY(WebIDL::to_usv_string(vm, js_member_value));
    }(); }));

                // 2. Set idlDict[key] to idlMemberValue.
                return idl_member_value;
            }
            // 7. Otherwise, jsMemberValue is undefined and the member is optional.
            return OptionalNone {};
        }()),
        .title = TRY([&]() -> JS::ThrowCompletionOr<String> {
            // 1. Let key be the identifier of member.
            // 2. If jsDict is either undefined or null, then:
            //     1. Let jsMemberValue be undefined.
            // 3. Otherwise,
            //     1. Let jsMemberValue be ? Get(jsDict, key).
            auto js_member_value = JS::js_undefined();
            if (js_dict.is_object())
                js_member_value = TRY(js_dict.as_object().get("title"_utf16_fly_string));

            // 4. If jsMemberValue is not undefined, then:
            if (!js_member_value.is_undefined()) {
                // 1. Let idlMemberValue be the result of converting jsMemberValue to an IDL value whose type is the type member is declared to be of.
                auto idl_member_value = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<String> {
        return TRY(WebIDL::to_string(vm, js_member_value));
    }(); }));

                // 2. Set idlDict[key] to idlMemberValue.
                return idl_member_value;
            }
            // 6. Otherwise, if jsMemberValue is undefined and member is required, then throw a TypeError.
            return vm.throw_completion<JS::TypeError>(JS::ErrorType::MissingRequiredProperty, "title");
        }()),
    };
}

} // namespace Web::Bindings
