#include <AK/String.h>
#include <AK/TypeCasts.h>
#include <AK/Variant.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/ArrayPrototype.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/Iterator.h>
#include <LibJS/Runtime/IteratorPrototype.h>
#include <LibJS/Runtime/PrimitiveString.h>
#include <LibJS/Runtime/PropertyKey.h>
#include <LibJS/Runtime/Realm.h>
#include <LibJS/Runtime/Value.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/URLSearchParams.h>
#include <LibWeb/DOMURL/URLSearchParams.h>
#include <LibWeb/DOMURL/URLSearchParamsIterator.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
#include <LibWeb/WebIDL/Tracing.h>
#include <LibWeb/WebIDL/Types.h>

namespace Web::Bindings {

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

    
    object.define_direct_property(vm.names.length, JS::Value(0), JS::Attribute::Configurable);
    object.define_direct_property(vm.names.name, JS::PrimitiveString::create(vm, "URLSearchParams"_utf16), JS::Attribute::Configurable);
    object.define_direct_property(vm.names.prototype, &ensure_web_prototype<URLSearchParamsPrototype>(realm, "URLSearchParams"_fly_string), 0);
}

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

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

    // 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<URLSearchParamsPrototype>(*target_realm, "URLSearchParams"_fly_string);
    }

    auto arg0 = vm.argument(0);
    Variant<Vector<Vector<String>>, OrderedHashMap<String, String>, String> init = Variant<Vector<Vector<String>>, OrderedHashMap<String, String>, String> { String {} };
    if (!arg0.is_undefined())
        init = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<Variant<Vector<Vector<String>>, OrderedHashMap<String, String>, String>> {

        if (arg0.is_object()) {
            [[maybe_unused]] auto& object = arg0.as_object();

            // 1. Let method be ? GetMethod(V, @@iterator).
            auto method = TRY(arg0.get_method(vm, vm.well_known_symbol_iterator()));

            // 2. If method is not undefined, return the result of creating a sequence of that type from V and method.
            if (method) {
                auto sequence_union_type = TRY([&]() -> JS::ThrowCompletionOr<Vector<Vector<String>>> {
        // 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, arg0, *method));

        Vector<Vector<String>> 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 [&]() -> JS::ThrowCompletionOr<Vector<String>> {
        if (!next_value.is_object())
            return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObject, next_value);

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

        return TRY([&]() -> JS::ThrowCompletionOr<Vector<String>> {
        // 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, next_value, *method));

        Vector<String> 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 [&]() -> JS::ThrowCompletionOr<String> {
        return TRY(WebIDL::to_usv_string(vm, next_value));
    }(); }));

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

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

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

        return sequence;
    }());
                return Variant<Vector<Vector<String>>, OrderedHashMap<String, String>, String> { sequence_union_type };
            }


            auto record_union_type = TRY([&]() -> JS::ThrowCompletionOr<OrderedHashMap<String, String>> {
        // An ECMAScript value O is converted to an IDL record<K, V> value as follows:
        // 1. If Type(O) is not Object, throw a TypeError.
        if (!arg0.is_object())
            return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObject, arg0);

        auto& record_object0 = arg0.as_object();

        // 2. Let result be a new empty instance of record<K, V>.
        OrderedHashMap<String, String> record;

        // 3. Let keys be ? O.[[OwnPropertyKeys]]().
        auto keys = TRY(record_object0.internal_own_property_keys());

        // 4. For each key of keys:
        for (auto& key : keys) {
            auto property_key = MUST(JS::PropertyKey::from_value(vm, key));

            // 1. Let desc be ? O.[[GetOwnProperty]](key).
            auto desc = TRY(record_object0.internal_get_own_property(property_key));

            // 2. If desc is not undefined and desc.[[Enumerable]] is true:
            if (!desc.has_value() || !desc->enumerable.has_value() || !desc->enumerable.value())
                continue;

            // 1. Let typedKey be key converted to an IDL value of type K.
            auto typed_key = TRY([&]() -> JS::ThrowCompletionOr<String> {
        return TRY(WebIDL::to_usv_string(vm, key));
    }());

            // 2. Let value be ? Get(O, key).
            auto value = TRY(record_object0.get(property_key));

            // 3. Let typedValue be value converted to an IDL value of type V.
            auto typed_value = TRY([&]() -> JS::ThrowCompletionOr<String> {
        return TRY(WebIDL::to_usv_string(vm, value));
    }());

            // 4. Set result[typedKey] to typedValue.
            record.set(typed_key, typed_value);
        }

        // 5. Return result.
        return record;
    }());
            return Variant<Vector<Vector<String>>, OrderedHashMap<String, String>, String> { record_union_type };
        }

        auto init_string = TRY([&]() -> JS::ThrowCompletionOr<String> {
        return TRY(WebIDL::to_usv_string(vm, arg0));
    }());
        return Variant<Vector<Vector<String>>, OrderedHashMap<String, String>, String> { init_string };

        return vm.throw_completion<JS::TypeError>("No union types matched"_utf16);
    }(); }));

    auto impl = TRY(throw_dom_exception_if_needed(vm, [&] { return DOMURL::URLSearchParams::construct_impl(realm, init); }));

    // 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 URLSearchParams" algorithm
    // (https://webidl.spec.whatwg.org/#js-platform-objects) are currently not handled, or are handled within DOMURL::URLSearchParams::construct_impl().

    return *impl;
}

GC_DEFINE_ALLOCATOR(URLSearchParamsPrototype);

URLSearchParamsPrototype::URLSearchParamsPrototype([[maybe_unused]] JS::Realm& realm)
    : Object(ConstructWithPrototypeTag::Tag, realm.intrinsics().object_prototype())
{
}

URLSearchParamsPrototype::~URLSearchParamsPrototype()
{
}


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

    object.set_prototype(realm.intrinsics().object_prototype());

    auto size_id = "size"_utf16_fly_string;
    auto native_size_getter = JS::NativeFunction::create(realm, size_getter, 0, size_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_size_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto size_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(size_id, native_size_getter, native_size_setter, size_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_native_function(realm, "append"_utf16_fly_string, append, 2, default_attributes);

    object.define_native_function(realm, "delete"_utf16_fly_string, delete_, 1, default_attributes);

    object.define_native_function(realm, "get"_utf16_fly_string, get, 1, default_attributes);

    object.define_native_function(realm, "getAll"_utf16_fly_string, get_all, 1, default_attributes);

    object.define_native_function(realm, "has"_utf16_fly_string, has, 1, default_attributes);

    object.define_native_function(realm, "set"_utf16_fly_string, set, 2, default_attributes);

    object.define_native_function(realm, "sort"_utf16_fly_string, sort, 0, default_attributes);

    object.define_native_function(realm, "toString"_utf16_fly_string, to_string, 0, default_attributes);


    object.define_native_function(realm, vm.names.entries, entries, 0, default_attributes);
    object.define_native_function(realm, vm.names.forEach, for_each, 1, default_attributes);
    object.define_native_function(realm, vm.names.keys, keys, 0, default_attributes);
    object.define_native_function(realm, vm.names.values, values, 0, default_attributes);

    object.define_direct_property(vm.well_known_symbol_iterator(), object.get_without_side_effects(vm.names.entries), JS::Attribute::Configurable | JS::Attribute::Writable);

    object.define_direct_property(vm.well_known_symbol_to_string_tag(), JS::PrimitiveString::create(vm, "URLSearchParams"_utf16), JS::Attribute::Configurable);
    Base::initialize(realm);
}

void URLSearchParamsPrototype::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<DOMURL::URLSearchParams*> impl_from(JS::VM& vm, JS::Value js_value)
{

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

[[maybe_unused]] static JS::ThrowCompletionOr<DOMURL::URLSearchParams*> 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(URLSearchParamsPrototype::size_getter)
{
    WebIDL::log_trace(vm, "URLSearchParamsPrototype::size_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->size(); }));

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

JS_DEFINE_NATIVE_FUNCTION(URLSearchParamsPrototype::append)
{
    WebIDL::log_trace(vm, "URLSearchParamsPrototype::append");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] DOMURL::URLSearchParams* idl_object = TRY(impl_from(vm));

    if (vm.argument_count() < 2)
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::BadArgCountMany, "append", "2");

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

    auto arg1 = vm.argument(1);
    auto value = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<String> {
        return TRY(WebIDL::to_usv_string(vm, arg1));
    }(); }));

    [[maybe_unused]] auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->append(name, value); }));
    return JS::js_undefined();
}

JS_DEFINE_NATIVE_FUNCTION(URLSearchParamsPrototype::delete_)
{
    WebIDL::log_trace(vm, "URLSearchParamsPrototype::delete_");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] DOMURL::URLSearchParams* idl_object = TRY(impl_from(vm));

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

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

    auto arg1 = vm.argument(1);
    Optional<String> value {};
    if (!arg1.is_undefined())
        value = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<String> {
        return TRY(WebIDL::to_usv_string(vm, arg1));
    }(); }));

    [[maybe_unused]] auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->delete_(name, value); }));
    return JS::js_undefined();
}

JS_DEFINE_NATIVE_FUNCTION(URLSearchParamsPrototype::get)
{
    WebIDL::log_trace(vm, "URLSearchParamsPrototype::get");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] DOMURL::URLSearchParams* idl_object = TRY(impl_from(vm));

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

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

    [[maybe_unused]] auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->get(name); }));
    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(WebIDL::primitive_string_from_string(vm, R.value()));
    }();
}

JS_DEFINE_NATIVE_FUNCTION(URLSearchParamsPrototype::get_all)
{
    WebIDL::log_trace(vm, "URLSearchParamsPrototype::get_all");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] DOMURL::URLSearchParams* idl_object = TRY(impl_from(vm));

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

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

    [[maybe_unused]] auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->get_all(name); }));
    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 = WebIDL::primitive_string_from_string(vm, sequence_element);

            // 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.
        }

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

JS_DEFINE_NATIVE_FUNCTION(URLSearchParamsPrototype::has)
{
    WebIDL::log_trace(vm, "URLSearchParamsPrototype::has");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] DOMURL::URLSearchParams* idl_object = TRY(impl_from(vm));

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

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

    auto arg1 = vm.argument(1);
    Optional<String> value {};
    if (!arg1.is_undefined())
        value = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<String> {
        return TRY(WebIDL::to_usv_string(vm, arg1));
    }(); }));

    [[maybe_unused]] auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->has(name, value); }));
    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(URLSearchParamsPrototype::set)
{
    WebIDL::log_trace(vm, "URLSearchParamsPrototype::set");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] DOMURL::URLSearchParams* idl_object = TRY(impl_from(vm));

    if (vm.argument_count() < 2)
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::BadArgCountMany, "set", "2");

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

    auto arg1 = vm.argument(1);
    auto value = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<String> {
        return TRY(WebIDL::to_usv_string(vm, arg1));
    }(); }));

    [[maybe_unused]] auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->set(name, value); }));
    return JS::js_undefined();
}

JS_DEFINE_NATIVE_FUNCTION(URLSearchParamsPrototype::sort)
{
    WebIDL::log_trace(vm, "URLSearchParamsPrototype::sort");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] DOMURL::URLSearchParams* idl_object = TRY(impl_from(vm));

    [[maybe_unused]] auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->sort(); }));
    return JS::js_undefined();
}

JS_DEFINE_NATIVE_FUNCTION(URLSearchParamsPrototype::to_string)
{
    WebIDL::log_trace(vm, "URLSearchParamsPrototype::to_string");
    auto* idl_object = TRY(impl_from(vm));
    auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->to_string(); }));
    return WebIDL::primitive_string_from_string(vm, R);
}

JS_DEFINE_NATIVE_FUNCTION(URLSearchParamsPrototype::entries)
{
    WebIDL::log_trace(vm, "URLSearchParamsPrototype::entries");

    // 1. Let jsValue be ? ToObject(this value).
    // 2. If jsValue is a platform object, then perform a security check, passing jsValue, "%Symbol.iterator%", and "method".
    // 3. If jsValue does not implement definition, then throw a TypeError.
    auto* this_impl = TRY(impl_from(vm));

    // 4. Return a newly created default iterator object for definition, with jsValue as its target, "key+value" as its kind, and index set to 0.
    return TRY(throw_dom_exception_if_needed(vm, [&] { return DOMURL::URLSearchParamsIterator::create(*this_impl, JS::Object::PropertyKind::KeyAndValue); }));
}

JS_DEFINE_NATIVE_FUNCTION(URLSearchParamsPrototype::keys)
{
    WebIDL::log_trace(vm, "URLSearchParamsPrototype::keys");

    // 1. Let jsValue be ? ToObject(this value).
    // 2. If jsValue is a platform object, then perform a security check, passing jsValue, "keys", and "method".
    // 3. If jsValue does not implement definition, then throw a TypeError.
    auto* this_impl = TRY(impl_from(vm));

    // 4. Return a newly created default iterator object for definition, with jsValue as its target, "key" as its kind, and index set to 0.
    return TRY(throw_dom_exception_if_needed(vm, [&] { return DOMURL::URLSearchParamsIterator::create(*this_impl, JS::Object::PropertyKind::Key); }));
}

JS_DEFINE_NATIVE_FUNCTION(URLSearchParamsPrototype::values)
{
    WebIDL::log_trace(vm, "URLSearchParamsPrototype::values");

    // 1. Let jsValue be ? ToObject(this value).
    // 2. If jsValue is a platform object, then perform a security check, passing jsValue, "values", and "method".
    // 3. If jsValue does not implement definition, then throw a TypeError.
    auto* this_impl = TRY(impl_from(vm));

    // 4. Return a newly created default iterator object for definition, with jsValue as its target, "value" as its kind, and index set to 0.
    return TRY(throw_dom_exception_if_needed(vm, [&] { return DOMURL::URLSearchParamsIterator::create(*this_impl, JS::Object::PropertyKind::Value); }));
}

JS_DEFINE_NATIVE_FUNCTION(URLSearchParamsPrototype::for_each)
{
    WebIDL::log_trace(vm, "URLSearchParamsPrototype::for_each");
    auto* this_impl = TRY(impl_from(vm));

    auto callback = vm.argument(0);
    if (!callback.is_function())
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAFunction, callback);

    auto this_value = vm.this_value();
    TRY(this_impl->for_each([&](auto key, auto value) -> JS::ThrowCompletionOr<void> {
        JS::Value wrapped_key = WebIDL::primitive_string_from_string(vm, key);
        JS::Value wrapped_value = WebIDL::primitive_string_from_string(vm, value);
        TRY(JS::call(vm, callback.as_function(), vm.argument(1), wrapped_value, wrapped_key, this_value));
        return {};
    }));

    return JS::js_undefined();
}

GC_DEFINE_ALLOCATOR(URLSearchParamsIteratorPrototype);

URLSearchParamsIteratorPrototype::URLSearchParamsIteratorPrototype(JS::Realm& realm)
    : Object(ConstructWithPrototypeTag::Tag, realm.intrinsics().iterator_prototype())
{
}

URLSearchParamsIteratorPrototype::~URLSearchParamsIteratorPrototype()
{
}

void URLSearchParamsIteratorPrototype::initialize(JS::Realm& realm)
{
    auto& vm = this->vm();
    Base::initialize(realm);
    define_native_function(realm, vm.names.next, next, 0, JS::Attribute::Writable | JS::Attribute::Enumerable | JS::Attribute::Configurable);
    define_direct_property(vm.well_known_symbol_to_string_tag(), JS::PrimitiveString::create(vm, "URLSearchParams Iterator"_utf16), JS::Attribute::Configurable);
}

static JS::ThrowCompletionOr<DOMURL::URLSearchParamsIterator*> url_search_params_iterator_impl_from(JS::VM& vm)
{
    auto this_object = TRY(vm.this_value().to_object(vm));
    if (!is<DOMURL::URLSearchParamsIterator>(*this_object))
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "URLSearchParamsIterator");
    return static_cast<DOMURL::URLSearchParamsIterator*>(this_object.ptr());
}

JS_DEFINE_NATIVE_FUNCTION(URLSearchParamsIteratorPrototype::next)
{
    WebIDL::log_trace(vm, "URLSearchParamsIteratorPrototype::next");
    auto* impl = TRY(url_search_params_iterator_impl_from(vm));
    return TRY(throw_dom_exception_if_needed(vm, [&] { return impl->next(); }));
}

} // namespace Web::Bindings
