#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/TypeCasts.h>
#include <AK/Utf16String.h>
#include <AK/Variant.h>
#include <AK/Vector.h>
#include <LibGC/Ptr.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/ArrayPrototype.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/IteratorPrototype.h>
#include <LibJS/Runtime/PrimitiveString.h>
#include <LibJS/Runtime/Realm.h>
#include <LibJS/Runtime/Value.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/FormData.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/FileAPI/Blob.h>
#include <LibWeb/FileAPI/File.h>
#include <LibWeb/Forward.h>
#include <LibWeb/HTML/HTMLElement.h>
#include <LibWeb/HTML/HTMLFormElement.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
#include <LibWeb/WebIDL/OverloadResolution.h>
#include <LibWeb/WebIDL/OverloadTypes.h>
#include <LibWeb/WebIDL/Tracing.h>
#include <LibWeb/XHR/FormData.h>
#include <LibWeb/XHR/FormDataIterator.h>

namespace Web::Bindings {

void FormDataConstructor::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, "FormData"_utf16), JS::Attribute::Configurable);
    object.define_direct_property(vm.names.prototype, &ensure_web_prototype<FormDataPrototype>(realm, "FormData"_fly_string), 0);
}

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

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

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

    auto arg0 = vm.argument(0);
    GC::Ptr<HTML::HTMLFormElement> form {};
    if (!arg0.is_undefined())
        form = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<GC::Ref<HTML::HTMLFormElement>> {
        if (auto impl = arg0.as_if<HTML::HTMLFormElement>())
            return *impl;
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "HTMLFormElement");
    }(); }));

    auto arg1 = vm.argument(1);
    GC::Ptr<HTML::HTMLElement> submitter = nullptr;
    if (!arg1.is_undefined())
        submitter = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<GC::Ptr<HTML::HTMLElement>> {
        GC::Ptr<HTML::HTMLElement> value;

        if (!arg1.is_nullish()) {

            value = TRY([&]() -> JS::ThrowCompletionOr<GC::Ref<HTML::HTMLElement>> {
        if (auto impl = arg1.as_if<HTML::HTMLElement>())
            return *impl;
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "HTMLElement");
    }());
        }
        return value;
    }(); }));

    auto impl = TRY(throw_dom_exception_if_needed(vm, [&] { return XHR::FormData::construct_impl(realm, form, submitter); }));

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

    return *impl;
}

GC_DEFINE_ALLOCATOR(FormDataPrototype);

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

FormDataPrototype::~FormDataPrototype()
{
}


void FormDataPrototype::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());
    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, 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, "FormData"_utf16), JS::Attribute::Configurable);
    Base::initialize(realm);
}

void FormDataPrototype::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<XHR::FormData*> impl_from(JS::VM& vm, JS::Value js_value)
{

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

[[maybe_unused]] static JS::ThrowCompletionOr<XHR::FormData*> 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(FormDataPrototype::append)
{
    WebIDL::log_trace(vm, "FormDataPrototype::append");

    Optional<int> chosen_overload_callable_id;
    Optional<WebIDL::EffectiveOverloadSet> effective_overload_set;

    switch (min(3, vm.argument_count())) {
    case 2: {
        Vector<WebIDL::EffectiveOverloadSet::Item> overloads;
        overloads.ensure_capacity(2);
        overloads.empend(0, Vector<NonnullRefPtr<WebIDL::Type const>> { make_ref_counted<WebIDL::Type>("Utf16USVString", false), make_ref_counted<WebIDL::Type>("Utf16USVString", false) }, Vector<WebIDL::Optionality> { WebIDL::Optionality::Required, WebIDL::Optionality::Required });
        overloads.empend(1, Vector<NonnullRefPtr<WebIDL::Type const>> { make_ref_counted<WebIDL::Type>("Utf16USVString", false), make_ref_counted<WebIDL::Type>("Blob", false) }, Vector<WebIDL::Optionality> { WebIDL::Optionality::Required, WebIDL::Optionality::Required });
        effective_overload_set.emplace(move(overloads), 1);
        break;
    }
    case 3:
        chosen_overload_callable_id = 1;
        break;
    }

    Vector<StringView> dictionary_types {
    };

    if (!chosen_overload_callable_id.has_value()) {
        if (!effective_overload_set.has_value())
            return vm.throw_completion<JS::TypeError>(JS::ErrorType::OverloadResolutionFailed);
        chosen_overload_callable_id = TRY(WebIDL::resolve_overload(vm, effective_overload_set.value(), dictionary_types)).callable_id;
    }


    switch (chosen_overload_callable_id.value()) {
    case 0:
        return append0(vm);
    case 1:
        return append1(vm);
    default:
        VERIFY_NOT_REACHED();
    }
}

JS_DEFINE_NATIVE_FUNCTION(FormDataPrototype::append0)
{
    WebIDL::log_trace(vm, "FormDataPrototype::append0");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] XHR::FormData* 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<Utf16String> {
        return TRY(WebIDL::to_utf16_usv_string(vm, arg0));
    }(); }));

    auto arg1 = vm.argument(1);
    auto value = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<Utf16String> {
        return TRY(WebIDL::to_utf16_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(FormDataPrototype::append1)
{
    WebIDL::log_trace(vm, "FormDataPrototype::append1");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] XHR::FormData* 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<Utf16String> {
        return TRY(WebIDL::to_utf16_usv_string(vm, arg0));
    }(); }));

    auto arg1 = vm.argument(1);
    auto blob_value = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<GC::Ref<FileAPI::Blob>> {
        if (auto impl = arg1.as_if<FileAPI::Blob>())
            return *impl;
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "Blob");
    }(); }));

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

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

JS_DEFINE_NATIVE_FUNCTION(FormDataPrototype::delete_)
{
    WebIDL::log_trace(vm, "FormDataPrototype::delete_");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] XHR::FormData* 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<Utf16String> {
        return TRY(WebIDL::to_utf16_usv_string(vm, arg0));
    }(); }));

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

JS_DEFINE_NATIVE_FUNCTION(FormDataPrototype::get)
{
    WebIDL::log_trace(vm, "FormDataPrototype::get");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] XHR::FormData* 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<Utf16String> {
        return TRY(WebIDL::to_utf16_usv_string(vm, arg0));
    }(); }));

    [[maybe_unused]] auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->get(name); }));
    return R.visit(
        [&](GC::Ref<FileAPI::File> const& visited_union_value0) -> JS::Value
        {
            return JS::Value(visited_union_value0);
        },
        [&](Utf16String const& visited_union_value1) -> JS::Value
        {
            return WebIDL::primitive_string_from_string(vm, visited_union_value1);
        },
        [](Empty) -> JS::Value
        {
            return JS::js_null();
        }
    );
}

JS_DEFINE_NATIVE_FUNCTION(FormDataPrototype::get_all)
{
    WebIDL::log_trace(vm, "FormDataPrototype::get_all");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] XHR::FormData* 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<Utf16String> {
        return TRY(WebIDL::to_utf16_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 = sequence_element.visit(
        [&](GC::Ref<FileAPI::File> const& visited_union_value0) -> JS::Value
        {
            return JS::Value(visited_union_value0);
        },
        [&](Utf16String const& visited_union_value1) -> JS::Value
        {
            return WebIDL::primitive_string_from_string(vm, visited_union_value1);
        }
    );

            // 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(FormDataPrototype::has)
{
    WebIDL::log_trace(vm, "FormDataPrototype::has");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] XHR::FormData* 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<Utf16String> {
        return TRY(WebIDL::to_utf16_usv_string(vm, arg0));
    }(); }));

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

JS_DEFINE_NATIVE_FUNCTION(FormDataPrototype::set)
{
    WebIDL::log_trace(vm, "FormDataPrototype::set");

    Optional<int> chosen_overload_callable_id;
    Optional<WebIDL::EffectiveOverloadSet> effective_overload_set;

    switch (min(3, vm.argument_count())) {
    case 2: {
        Vector<WebIDL::EffectiveOverloadSet::Item> overloads;
        overloads.ensure_capacity(2);
        overloads.empend(0, Vector<NonnullRefPtr<WebIDL::Type const>> { make_ref_counted<WebIDL::Type>("Utf16USVString", false), make_ref_counted<WebIDL::Type>("Utf16USVString", false) }, Vector<WebIDL::Optionality> { WebIDL::Optionality::Required, WebIDL::Optionality::Required });
        overloads.empend(1, Vector<NonnullRefPtr<WebIDL::Type const>> { make_ref_counted<WebIDL::Type>("Utf16USVString", false), make_ref_counted<WebIDL::Type>("Blob", false) }, Vector<WebIDL::Optionality> { WebIDL::Optionality::Required, WebIDL::Optionality::Required });
        effective_overload_set.emplace(move(overloads), 1);
        break;
    }
    case 3:
        chosen_overload_callable_id = 1;
        break;
    }

    Vector<StringView> dictionary_types {
    };

    if (!chosen_overload_callable_id.has_value()) {
        if (!effective_overload_set.has_value())
            return vm.throw_completion<JS::TypeError>(JS::ErrorType::OverloadResolutionFailed);
        chosen_overload_callable_id = TRY(WebIDL::resolve_overload(vm, effective_overload_set.value(), dictionary_types)).callable_id;
    }


    switch (chosen_overload_callable_id.value()) {
    case 0:
        return set0(vm);
    case 1:
        return set1(vm);
    default:
        VERIFY_NOT_REACHED();
    }
}

JS_DEFINE_NATIVE_FUNCTION(FormDataPrototype::set0)
{
    WebIDL::log_trace(vm, "FormDataPrototype::set0");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] XHR::FormData* 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<Utf16String> {
        return TRY(WebIDL::to_utf16_usv_string(vm, arg0));
    }(); }));

    auto arg1 = vm.argument(1);
    auto value = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<Utf16String> {
        return TRY(WebIDL::to_utf16_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(FormDataPrototype::set1)
{
    WebIDL::log_trace(vm, "FormDataPrototype::set1");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] XHR::FormData* 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<Utf16String> {
        return TRY(WebIDL::to_utf16_usv_string(vm, arg0));
    }(); }));

    auto arg1 = vm.argument(1);
    auto blob_value = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<GC::Ref<FileAPI::Blob>> {
        if (auto impl = arg1.as_if<FileAPI::Blob>())
            return *impl;
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "Blob");
    }(); }));

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

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

JS_DEFINE_NATIVE_FUNCTION(FormDataPrototype::entries)
{
    WebIDL::log_trace(vm, "FormDataPrototype::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 XHR::FormDataIterator::create(*this_impl, JS::Object::PropertyKind::KeyAndValue); }));
}

JS_DEFINE_NATIVE_FUNCTION(FormDataPrototype::keys)
{
    WebIDL::log_trace(vm, "FormDataPrototype::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 XHR::FormDataIterator::create(*this_impl, JS::Object::PropertyKind::Key); }));
}

JS_DEFINE_NATIVE_FUNCTION(FormDataPrototype::values)
{
    WebIDL::log_trace(vm, "FormDataPrototype::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 XHR::FormDataIterator::create(*this_impl, JS::Object::PropertyKind::Value); }));
}

JS_DEFINE_NATIVE_FUNCTION(FormDataPrototype::for_each)
{
    WebIDL::log_trace(vm, "FormDataPrototype::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 = value.visit(
        [&](GC::Ref<FileAPI::File> const& visited_union_value0) -> JS::Value
        {
            return JS::Value(visited_union_value0);
        },
        [&](Utf16String const& visited_union_value1) -> JS::Value
        {
            return WebIDL::primitive_string_from_string(vm, visited_union_value1);
        }
    );
        TRY(JS::call(vm, callback.as_function(), vm.argument(1), wrapped_value, wrapped_key, this_value));
        return {};
    }));

    return JS::js_undefined();
}

GC_DEFINE_ALLOCATOR(FormDataIteratorPrototype);

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

FormDataIteratorPrototype::~FormDataIteratorPrototype()
{
}

void FormDataIteratorPrototype::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, "FormData Iterator"_utf16), JS::Attribute::Configurable);
}

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

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

} // namespace Web::Bindings
