#include <AK/Utf16String.h>
#include <AK/Variant.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/Realm.h>
#include <LibJS/Runtime/VM.h>
#include <LibJS/Runtime/Value.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibWeb/Bindings/DOMParser.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/HTML/DOMParser.h>
#include <LibWeb/TrustedTypes/TrustedHTML.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
#include <LibWeb/WebIDL/Tracing.h>

namespace Web::Bindings {

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

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

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

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

    auto impl = TRY(throw_dom_exception_if_needed(vm, [&] { return HTML::DOMParser::construct_impl(realm); }));

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

    return *impl;
}

void DOMParserPrototype::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(realm.intrinsics().object_prototype());
    object.define_native_function(realm, "parseFromString"_utf16_fly_string, parse_from_string, 2, default_attributes);

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

void DOMParserPrototype::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<HTML::DOMParser*> impl_from(JS::VM& vm, JS::Value js_value)
{

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

[[maybe_unused]] static JS::ThrowCompletionOr<HTML::DOMParser*> 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(DOMParserPrototype::parse_from_string)
{
    WebIDL::log_trace(vm, "DOMParserPrototype::parse_from_string");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] HTML::DOMParser* idl_object = TRY(impl_from(vm));

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

    auto arg0 = vm.argument(0);
    auto string = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<Variant<GC::Ref<TrustedTypes::TrustedHTML>, Utf16String>> {

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

            if (is<PlatformObject>(object)) {

                if (auto* result = as_if<TrustedTypes::TrustedHTML>(object))
                    return Variant<GC::Ref<TrustedTypes::TrustedHTML>, Utf16String> { GC::Ref { *result } };
            }
        }

        auto string_string = TRY([&]() -> JS::ThrowCompletionOr<Utf16String> {
        return TRY(WebIDL::to_utf16_string(vm, arg0));
    }());
        return Variant<GC::Ref<TrustedTypes::TrustedHTML>, Utf16String> { string_string };

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

    auto arg1 = vm.argument(1);
    auto type = TRY(throw_dom_exception_if_needed(vm, [&] { return convert_to_idl_value_for_dom_parser_supported_type(vm, arg1); }));

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

// https://webidl.spec.whatwg.org/#idl-enumeration
JS::ThrowCompletionOr<DOMParserSupportedType> convert_to_idl_value_for_dom_parser_supported_type(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 == "text/html"sv)
        return DOMParserSupportedType::Text_Html;
    if (value_as_string == "text/xml"sv)
        return DOMParserSupportedType::Text_Xml;
    if (value_as_string == "application/xml"sv)
        return DOMParserSupportedType::Application_Xml;
    if (value_as_string == "application/xhtml+xml"sv)
        return DOMParserSupportedType::Application_XhtmlXml;
    if (value_as_string == "image/svg+xml"sv)
        return DOMParserSupportedType::Image_SvgXml;
    return vm.throw_completion<JS::TypeError>(JS::ErrorType::InvalidEnumerationValue, value_as_string, "DOMParserSupportedType");
}

// https://webidl.spec.whatwg.org/#idl-enumeration
Utf16String idl_enum_to_string(DOMParserSupportedType 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 DOMParserSupportedType::Text_Html:
        return "text/html"_utf16;
    case DOMParserSupportedType::Text_Xml:
        return "text/xml"_utf16;
    case DOMParserSupportedType::Application_Xml:
        return "application/xml"_utf16;
    case DOMParserSupportedType::Application_XhtmlXml:
        return "application/xhtml+xml"_utf16;
    case DOMParserSupportedType::Image_SvgXml:
        return "image/svg+xml"_utf16;
    }
    VERIFY_NOT_REACHED();
}

} // namespace Web::Bindings
