/*
 * Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
 * Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <LibJS/Runtime/FunctionObject.h>
#include <LibJS/Runtime/Iterator.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibWeb/Bindings/CustomElementRegistry.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/ElementFactory.h>
#include <LibWeb/DOM/ShadowRoot.h>
#include <LibWeb/HTML/CustomElements/CustomElementName.h>
#include <LibWeb/HTML/CustomElements/CustomElementReactionNames.h>
#include <LibWeb/HTML/CustomElements/CustomElementRegistry.h>
#include <LibWeb/HTML/Scripting/Environments.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/Namespace.h>

namespace Web::HTML {

GC_DEFINE_ALLOCATOR(CustomElementRegistry);
GC_DEFINE_ALLOCATOR(CustomElementDefinition);

// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry
GC::Ref<CustomElementRegistry> CustomElementRegistry::construct_impl(JS::Realm& realm)
{
    // The new CustomElementRegistry() constructor steps are to set this's is scoped to true.
    auto registry = realm.create<CustomElementRegistry>(realm);
    registry->m_is_scoped = true;
    return registry;
}

CustomElementRegistry::CustomElementRegistry(JS::Realm& realm)
    : Bindings::PlatformObject(realm)
{
}

CustomElementRegistry::~CustomElementRegistry() = default;

void CustomElementRegistry::initialize(JS::Realm& realm)
{
    WEB_SET_PROTOTYPE_FOR_INTERFACE(CustomElementRegistry);
    Base::initialize(realm);
}

void CustomElementRegistry::visit_edges(Visitor& visitor)
{
    Base::visit_edges(visitor);
    visitor.visit(m_custom_element_definitions);
    visitor.visit(m_when_defined_promise_map);
}

// https://webidl.spec.whatwg.org/#es-callback-function
// https://github.com/whatwg/html/pull/9893
static JS::ThrowCompletionOr<GC::Ref<WebIDL::CallbackType>> convert_value_to_callback_function(JS::VM& vm, JS::Value value)
{
    // FIXME: De-duplicate this from the IDL generator.
    // 1. If the result of calling IsCallable(V) is false and the conversion to an IDL value is not being performed due to V being assigned to an attribute whose type is a nullable callback function that is annotated with [LegacyTreatNonObjectAsNull], then throw a TypeError.
    if (!value.is_function())
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAFunction, value);

    // 2. Return the IDL callback function type value that represents a reference to the same object that V represents, with the incumbent realm as the callback context.
    return vm.heap().allocate<WebIDL::CallbackType>(value.as_object(), HTML::incumbent_realm());
}

// https://webidl.spec.whatwg.org/#es-sequence
static JS::ThrowCompletionOr<Vector<String>> convert_value_to_sequence_of_strings(JS::VM& vm, JS::Value value)
{
    // FIXME: De-duplicate this from the IDL generator.
    // An ECMAScript value V is converted to an IDL sequence<T> value as follows:
    // 1. If V is not an Object, throw a TypeError.
    if (!value.is_object())
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObject, value);

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

    // 3. If method is undefined, throw a TypeError.
    if (!method)
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotIterable, value);

    // 4. Return the result of creating a sequence from V and method.

    // https://webidl.spec.whatwg.org/#create-sequence-from-iterable
    // To create an IDL value of type sequence<T> given an iterable iterable and an iterator getter method, perform the following steps:
    // 1. Let iter be ? GetIterator(iterable, sync, method).
    // FIXME: The WebIDL spec is out of date - it should be using GetIteratorFromMethod.
    auto iterator = TRY(JS::get_iterator_from_method(vm, value, *method));

    // 2. Initialize i to be 0.
    Vector<String> sequence_of_strings;

    // 3. Repeat
    for (;;) {
        // 1. Let next be ? IteratorStep(iter).
        auto next = TRY(JS::iterator_step(vm, iterator));

        // 2. If next is false, 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<JS::IterationResult>())
            return sequence_of_strings;

        // 3. Let nextItem be ? IteratorValue(next).
        auto next_item = TRY(next.get<JS::IterationResult>().value);

        // 4. Initialize Si to the result of converting nextItem to an IDL value of type T.

        // https://webidl.spec.whatwg.org/#es-DOMString
        // An ECMAScript value V is converted to an IDL DOMString value by running the following algorithm:
        // 1. If V is null and the conversion is to an IDL type associated with the [LegacyNullToEmptyString] extended attribute, then return the DOMString value that represents the empty string.
        // NOTE: This doesn't apply.

        // 2. Let x be ? ToString(V).
        // 3. Return the IDL DOMString value that represents the same sequence of code units as the one the ECMAScript String value x represents.
        auto string_value = TRY(next_item.to_string(vm));

        sequence_of_strings.append(move(string_value));

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

// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-define
JS::ThrowCompletionOr<void> CustomElementRegistry::define(String const& name, WebIDL::CallbackType* constructor, Bindings::ElementDefinitionOptions const& options)
{
    auto& realm = this->realm();
    auto& vm = this->vm();

    // 1. If IsConstructor(constructor) is false, then throw a TypeError.
    if (!JS::Value(constructor->callback).is_constructor())
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAConstructor, JS::Value(constructor->callback));

    // 2. If name is not a valid custom element name, then throw a "SyntaxError" DOMException.
    if (!is_valid_custom_element_name(name))
        return JS::throw_completion(WebIDL::SyntaxError::create(realm, Utf16String::formatted("'{}' is not a valid custom element name", name)));

    // 3. If this's custom element definition set contains an item with name name, then throw a "NotSupportedError"
    //    DOMException.
    auto existing_definition_with_name_iterator = m_custom_element_definitions.find_if([&name](auto const& definition) {
        return definition->name() == name;
    });

    if (existing_definition_with_name_iterator != m_custom_element_definitions.end())
        return JS::throw_completion(WebIDL::NotSupportedError::create(realm, Utf16String::formatted("A custom element with name '{}' is already defined", name)));

    // 4. If this's custom element definition set contains an item with constructor constructor, then throw a
    //    "NotSupportedError" DOMException.
    auto existing_definition_with_constructor_iterator = m_custom_element_definitions.find_if([&constructor](auto const& definition) {
        return definition->constructor().callback == constructor->callback;
    });

    if (existing_definition_with_constructor_iterator != m_custom_element_definitions.end())
        return JS::throw_completion(WebIDL::NotSupportedError::create(realm, "The given constructor is already in use by another custom element"_utf16));

    // 5. Let localName be name.
    String local_name = name;

    // 6. Let extends be options["extends"] if it exists; otherwise null.
    auto& extends = options.extends;

    // 7. If extends is not null:
    if (extends.has_value()) {
        // 1. If this's is scoped is true, then throw a "NotSupportedError" DOMException.
        if (m_is_scoped)
            return JS::throw_completion(WebIDL::NotSupportedError::create(realm, "Cannot define a custom element that extends another in a scoped registry"_utf16));

        // 2. If extends is a valid custom element name, then throw a "NotSupportedError" DOMException.
        if (is_valid_custom_element_name(extends.value()))
            return JS::throw_completion(WebIDL::NotSupportedError::create(realm, Utf16String::formatted("'{}' is a custom element name, only non-custom elements can be extended", extends.value())));

        // 3. If the element interface for extends and the HTML namespace is HTMLUnknownElement (e.g., if extends does
        //    not indicate an element definition in this specification), then throw a "NotSupportedError" DOMException.
        if (DOM::is_unknown_html_element(extends.value()))
            return JS::throw_completion(WebIDL::NotSupportedError::create(realm, Utf16String::formatted("'{}' is an unknown HTML element", extends.value())));

        // 4. Set localName to extends.
        local_name = extends.value();
    }

    // 8. If this's element definition is running is true, then throw a "NotSupportedError" DOMException.
    if (m_element_definition_is_running)
        return JS::throw_completion(WebIDL::NotSupportedError::create(realm, "Cannot recursively define custom elements"_utf16));

    // 9. Set this's element definition is running to true.
    m_element_definition_is_running = true;

    // 10. Let formAssociated be false.
    bool form_associated = false;

    // 11. Let disableInternals be false.
    bool disable_internals = false;

    // 12. Let disableShadow be false.
    bool disable_shadow = false;

    // 13. Let observedAttributes be an empty sequence<DOMString>.
    Vector<String> observed_attributes;

    // 14. Run the following steps while catching any exceptions:
    OrderedHashMap<FlyString, GC::Root<WebIDL::CallbackType>> lifecycle_callbacks;
    auto get_definition_attributes_from_constructor = [&]() -> JS::ThrowCompletionOr<void> {
        // 1. Let prototype be ? Get(constructor, "prototype").
        auto prototype_value = TRY(constructor->callback->get(vm.names.prototype));

        // 2. If prototype is not an Object, then throw a TypeError exception.
        auto prototype = prototype_value.as_if<JS::Object>();
        if (!prototype)
            return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObject, prototype_value);

        // 3. Let lifecycleCallbacks be the ordered map «[ "connectedCallback" → null, "disconnectedCallback" → null,
        //    "connectedMoveCallback" → null, "adoptedCallback" → null, "attributeChangedCallback" → null ]».
        lifecycle_callbacks.set(CustomElementReactionNames::connectedCallback, {});
        lifecycle_callbacks.set(CustomElementReactionNames::disconnectedCallback, {});
        lifecycle_callbacks.set(CustomElementReactionNames::connectedMoveCallback, {});
        lifecycle_callbacks.set(CustomElementReactionNames::adoptedCallback, {});
        lifecycle_callbacks.set(CustomElementReactionNames::attributeChangedCallback, {});

        // 4. For each callbackName of the keys of lifecycleCallbacks:
        for (auto const& callback_name : { CustomElementReactionNames::connectedCallback, CustomElementReactionNames::disconnectedCallback, CustomElementReactionNames::connectedMoveCallback, CustomElementReactionNames::adoptedCallback, CustomElementReactionNames::attributeChangedCallback }) {

            // 1. Let callbackValue be ? Get(prototype, callbackName).
            auto callback_value = TRY(prototype->get(Utf16FlyString::from_utf8(callback_name)));

            // 2. If callbackValue is not undefined, then set lifecycleCallbacks[callbackName] to the result of
            //    converting callbackValue to the Web IDL Function callback type.
            if (!callback_value.is_undefined()) {
                auto callback = TRY(convert_value_to_callback_function(vm, callback_value));
                lifecycle_callbacks.set(callback_name, callback);
            }
        }

        // 5. If lifecycleCallbacks["attributeChangedCallback"] is not null:
        auto attribute_changed_callback_iterator = lifecycle_callbacks.find(CustomElementReactionNames::attributeChangedCallback);
        VERIFY(attribute_changed_callback_iterator != lifecycle_callbacks.end());
        if (attribute_changed_callback_iterator->value) {
            // 1. Let observedAttributesIterable be ? Get(constructor, "observedAttributes").
            auto observed_attributes_iterable = TRY(constructor->callback->get(vm.names.observedAttributes));

            // 2. If observedAttributesIterable is not undefined, then set observedAttributes to the result of
            //    converting observedAttributesIterable to a sequence<DOMString>. Rethrow any exceptions from the
            //    conversion.
            if (!observed_attributes_iterable.is_undefined())
                observed_attributes = TRY(convert_value_to_sequence_of_strings(vm, observed_attributes_iterable));
        }

        // 6. Let disabledFeatures be an empty sequence<DOMString>.
        Vector<String> disabled_features;

        // 7. Let disabledFeaturesIterable be ? Get(constructor, "disabledFeatures").
        auto disabled_features_iterable = TRY(constructor->callback->get(vm.names.disabledFeatures));

        // 8. If disabledFeaturesIterable is not undefined, then set disabledFeatures to the result of converting
        //    disabledFeaturesIterable to a sequence<DOMString>. Rethrow any exceptions from the conversion.
        if (!disabled_features_iterable.is_undefined())
            disabled_features = TRY(convert_value_to_sequence_of_strings(vm, disabled_features_iterable));

        // 9. If disabledFeatures contains "internals", then set disableInternals to true.
        disable_internals = disabled_features.contains_slow("internals"sv);

        // 10. If disabledFeatures contains "shadow", then set disableShadow to true.
        disable_shadow = disabled_features.contains_slow("shadow"sv);

        // 11. Let formAssociatedValue be ? Get( constructor, "formAssociated").
        auto form_associated_value = TRY(constructor->callback->get(vm.names.formAssociated));

        // 12. Set formAssociated to the result of converting formAssociatedValue to a boolean.
        form_associated = form_associated_value.to_boolean();

        // 13. If formAssociated is true, then for each callbackName of « "formAssociatedCallback",
        //     "formResetCallback", "formDisabledCallback", "formStateRestoreCallback" »:
        if (form_associated) {
            for (auto const& callback_name : { CustomElementReactionNames::formAssociatedCallback, CustomElementReactionNames::formResetCallback, CustomElementReactionNames::formDisabledCallback, CustomElementReactionNames::formStateRestoreCallback }) {
                // 1. Let callbackValue be ? Get(prototype, callbackName).
                auto callback_value = TRY(prototype->get(Utf16FlyString::from_utf8(callback_name)));

                // 2. If callbackValue is not undefined, then set lifecycleCallbacks[callbackName] to the result of
                //    converting callbackValue to the Web IDL Function callback type.
                if (!callback_value.is_undefined())
                    lifecycle_callbacks.set(callback_name, TRY(convert_value_to_callback_function(vm, callback_value)));
            }
        }

        return {};
    };

    auto maybe_exception = get_definition_attributes_from_constructor();

    //     Then, regardless of whether the above steps threw an exception or not: set this's element definition is
    //     running to false.
    m_element_definition_is_running = false;

    //     Finally, if the steps threw an exception, rethrow that exception.
    if (maybe_exception.is_throw_completion())
        return maybe_exception.release_error();

    // 15. Let definition be a new custom element definition with name name, local name localName, constructor
    //     constructor, observed attributes observedAttributes, lifecycle callbacks lifecycleCallbacks, form-associated
    //     formAssociated, disable internals disableInternals, and disable shadow disableShadow.
    auto definition = CustomElementDefinition::create(realm, name, local_name, *constructor, move(observed_attributes), move(lifecycle_callbacks), form_associated, disable_internals, disable_shadow);

    // 16. Append definition to this's custom element definition set.
    m_custom_element_definitions.append(definition);

    // 17. If this's is scoped is true, then for each document of this's scoped document set:
    //     upgrade particular elements within a document given this, document, definition, and localName.
    if (m_is_scoped) {
        for (auto& document : m_scoped_documents)
            document.upgrade_particular_elements(*this, definition, local_name);
    }
    // 18. Otherwise, upgrade particular elements within a document given this, this's relevant global object's
    //     associated Document, definition, localName, and name.
    else {
        auto& document = as<HTML::Window>(relevant_global_object(*this)).associated_document();
        document.upgrade_particular_elements(*this, definition, local_name, name);
    }

    // 19. If this's when-defined promise map[name] exists:
    auto promise_when_defined_iterator = m_when_defined_promise_map.find(name);
    if (promise_when_defined_iterator != m_when_defined_promise_map.end()) {
        // 1. Resolve this's when-defined promise map[name] with constructor.
        WebIDL::resolve_promise(realm, promise_when_defined_iterator->value, constructor->callback);

        // 2. Remove this's when-defined promise map[name].
        m_when_defined_promise_map.remove(name);
    }

    return {};
}

// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-get
Variant<GC::Root<WebIDL::CallbackType>, Empty> CustomElementRegistry::get(String const& name) const
{
    // 1. If this's custom element definition set contains an item with name name, then return that item's constructor.
    auto existing_definition_iterator = m_custom_element_definitions.find_if([&name](auto const& definition) {
        return definition->name() == name;
    });

    if (!existing_definition_iterator.is_end())
        return GC::make_root((*existing_definition_iterator)->constructor());

    // 2. Return undefined.
    return Empty {};
}

// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-getname
Optional<String> CustomElementRegistry::get_name(GC::Root<WebIDL::CallbackType> const& constructor) const
{
    // 1. If this's custom element definition set contains an item with constructor constructor, then return that item's name.
    auto existing_definition_iterator = m_custom_element_definitions.find_if([&constructor](auto const& definition) {
        return definition->constructor().callback == constructor.cell()->callback;
    });

    if (!existing_definition_iterator.is_end())
        return (*existing_definition_iterator)->name();

    // 2. Return null.
    return {};
}

// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-whendefined
WebIDL::ExceptionOr<GC::Ref<WebIDL::Promise>> CustomElementRegistry::when_defined(String const& name)
{
    auto& realm = this->realm();

    // 1. If name is not a valid custom element name, then return a promise rejected with a "SyntaxError" DOMException.
    if (!is_valid_custom_element_name(name))
        return WebIDL::create_rejected_promise(realm, WebIDL::SyntaxError::create(realm, Utf16String::formatted("'{}' is not a valid custom element name", name)));

    // 2. If this's custom element definition set contains an item with name name, then return a promise resolved with that item's constructor.
    auto existing_definition_iterator = m_custom_element_definitions.find_if([&name](auto const& definition) {
        return definition->name() == name;
    });

    if (existing_definition_iterator != m_custom_element_definitions.end())
        return WebIDL::create_resolved_promise(realm, (*existing_definition_iterator)->constructor().callback);

    // 3. If this's when-defined promise map[name] does not exist, then set this's when-defined promise map[name] to a new promise.
    auto existing_promise_iterator = m_when_defined_promise_map.find(name);
    GC::Ptr<WebIDL::Promise> promise;
    if (existing_promise_iterator == m_when_defined_promise_map.end()) {
        promise = WebIDL::create_promise(realm);
        m_when_defined_promise_map.set(name, *promise);
    } else {
        promise = existing_promise_iterator->value;
    }

    // 4. Return this's when-defined promise map[name].
    VERIFY(promise);
    return GC::Ref { *promise };
}

// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-upgrade
void CustomElementRegistry::upgrade(GC::Ref<DOM::Node> root) const
{
    // 1. For each shadow-including inclusive descendant candidate of root, in shadow-including tree order:
    root->for_each_shadow_including_inclusive_descendant([&](DOM::Node& candidate) {
        // 1. If candidate is not an Element node, then continue.
        auto* element = as_if<DOM::Element>(candidate);
        if (!element)
            return TraversalDecision::Continue;

        // 2. If candidate's custom element registry is not this, then continue.
        if (element->custom_element_registry() != this)
            return TraversalDecision::Continue;

        // 3. Try to upgrade candidate.
        element->try_to_upgrade();
        return TraversalDecision::Continue;
    });
}

// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-initialize
WebIDL::ExceptionOr<void> CustomElementRegistry::initialize_for_bindings(GC::Ref<DOM::Node> root)
{
    // 1. If this's is scoped is false and either root is a Document node or root's node document's custom element
    //    registry is not this, then throw a "NotSupportedError" DOMException.
    if (!is_scoped() && (root->is_document() || root->document().custom_element_registry() != this))
        return WebIDL::NotSupportedError::create(realm(), "CustomElementRegistry must either be scoped or the document's custom element registry."_utf16);

    // 2. If root is a Document node whose custom element registry is null, then set root's custom element registry to
    //    this.
    if (auto* document = as_if<DOM::Document>(*root); document && !document->custom_element_registry())
        document->set_custom_element_registry(this);

    // 3. Otherwise, if root is a ShadowRoot node whose custom element registry is null, then set root's custom element
    //    registry to this.
    else if (auto* shadow_root = as_if<DOM::ShadowRoot>(*root); shadow_root && !shadow_root->custom_element_registry())
        shadow_root->set_custom_element_registry(this);

    // 4. For each inclusive descendant inclusiveDescendant of root, in tree order:
    root->for_each_in_inclusive_subtree([this](auto& inclusive_descendant) {
        // 1. If inclusiveDescendant is not an Element node, then continue.
        auto* element = as_if<DOM::Element>(inclusive_descendant);
        if (!element)
            return TraversalDecision::Continue;

        // 2. If inclusiveDescendant's custom element registry is null:
        if (!element->custom_element_registry()) {
            // 1. Set inclusiveDescendant's custom element registry to this.
            element->set_custom_element_registry(this);

            // 2. If this's is scoped is true, then append inclusiveDescendant's node document to this's scoped
            //    document set.
            if (m_is_scoped)
                append_scoped_document(element->document());
        }

        // 3. If inclusiveDescendant's custom element registry is not this, then continue.
        if (element->custom_element_registry() != this)
            return TraversalDecision::Continue;

        // 4. Try to upgrade inclusiveDescendant.
        element->try_to_upgrade();

        return TraversalDecision::Continue;
    });

    return {};
}

void CustomElementRegistry::append_scoped_document(GC::Ref<DOM::Document> document)
{
    m_scoped_documents.set(document);
}

GC::Ptr<CustomElementDefinition> CustomElementRegistry::get_definition_with_name_and_local_name(String const& name, String const& local_name) const
{
    auto definition_iterator = m_custom_element_definitions.find_if([&](auto const& definition) {
        return definition->name() == name && definition->local_name() == local_name;
    });

    return definition_iterator.is_end() ? nullptr : definition_iterator->ptr();
}

GC::Ptr<CustomElementDefinition> CustomElementRegistry::get_definition_from_new_target(JS::FunctionObject const& new_target) const
{
    auto definition_iterator = m_custom_element_definitions.find_if([&](auto const& definition) {
        return definition->constructor().callback.ptr() == &new_target;
    });

    return definition_iterator.is_end() ? nullptr : definition_iterator->ptr();
}

// https://html.spec.whatwg.org/multipage/custom-elements.html#look-up-a-custom-element-registry
GC::Ptr<CustomElementRegistry> look_up_a_custom_element_registry(DOM::Node const& node)
{
    // To look up a custom element registry, given a Node object node:
    // 1. If node is an Element object, then return node's custom element registry.
    if (auto* element = as_if<DOM::Element>(node))
        return element->custom_element_registry();

    // 2. If node is a ShadowRoot object, then return node's custom element registry.
    if (auto* shadow_root = as_if<DOM::ShadowRoot>(node))
        return shadow_root->custom_element_registry();

    // 3. If node is a Document object, then return node's custom element registry.
    if (auto* document = as_if<DOM::Document>(node))
        return document->custom_element_registry();

    // 4. Return null.
    return nullptr;
}

// https://html.spec.whatwg.org/multipage/custom-elements.html#look-up-a-custom-element-definition
GC::Ptr<CustomElementDefinition> look_up_a_custom_element_definition(GC::Ptr<CustomElementRegistry> registry, Optional<FlyString> const& namespace_, FlyString const& local_name, Optional<String> const& is)
{
    // 1. If registry is null, then return null.
    if (!registry)
        return nullptr;

    // 2. If namespace is not the HTML namespace, then return null.
    if (namespace_ != Namespace::HTML)
        return nullptr;

    // 3. If registry's custom element definition set contains an item with name and local name both equal to
    //    localName, then return that item.
    auto converted_local_name = local_name.to_string();
    if (auto maybe_definition = registry->get_definition_with_name_and_local_name(converted_local_name, converted_local_name))
        return maybe_definition;

    // 4. If registry's custom element definition set contains an item with name equal to is and local name equal to
    //    localName, then return that item.
    // 5. Return null.

    // NB: If `is` has no value, it can never match as custom element definitions always have a name and localName
    //     (i.e. not stored as Optional<String>)
    if (!is.has_value())
        return nullptr;

    return registry->get_definition_with_name_and_local_name(is.value(), converted_local_name);
}

// https://dom.spec.whatwg.org/#is-a-global-custom-element-registry
bool is_a_global_custom_element_registry(GC::Ptr<CustomElementRegistry> registry)
{
    // Null or a CustomElementRegistry object registry is a global custom element registry if registry is non-null and
    // registry’s is scoped is false.
    return registry && !registry->is_scoped();
}

}
