#include <AK/Utf16FlyString.h>
#include <AK/Utf16String.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/Value.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibWeb/Bindings/DOMImplementation.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/DOM/DOMImplementation.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/DocumentType.h>
#include <LibWeb/DOM/XMLDocument.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
#include <LibWeb/WebIDL/Tracing.h>

namespace Web::Bindings {

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

JS::ThrowCompletionOr<GC::Ref<JS::Object>> DOMImplementationConstructor::construct([[maybe_unused]] InterfaceConstructor& constructor, [[maybe_unused]] JS::FunctionObject& new_target)
{
    WebIDL::log_trace(constructor.vm(), "DOMImplementationConstructor::construct");
    return constructor.vm().throw_completion<JS::TypeError>(JS::ErrorType::NotAConstructor, "DOMImplementation");
}

void DOMImplementationPrototype::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, "createDocumentType"_utf16_fly_string, create_document_type, 3, default_attributes);

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

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

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

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

void DOMImplementationPrototype::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<DOM::DOMImplementation*> impl_from(JS::VM& vm, JS::Value js_value)
{

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

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

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

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

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

    auto arg2 = vm.argument(2);
    auto system_id = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<Utf16String> {
        return TRY(WebIDL::to_utf16_string(vm, arg2));
    }(); }));

    [[maybe_unused]] auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->create_document_type(qualified_name, public_id, system_id); }));
    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(DOMImplementationPrototype::create_document)
{
    WebIDL::log_trace(vm, "DOMImplementationPrototype::create_document");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] DOM::DOMImplementation* idl_object = TRY(impl_from(vm));

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

    auto arg0 = vm.argument(0);
    auto namespace_ = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<Optional<Utf16FlyString>> {
        Optional<Utf16FlyString> value;

        if (!arg0.is_nullish()) {

            value = TRY([&]() -> JS::ThrowCompletionOr<Utf16FlyString> {
        return TRY(WebIDL::to_utf16_string(vm, arg0));
    }());
        }
        return value;
    }(); }));

    auto arg1 = vm.argument(1);
    auto qualified_name = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<Utf16FlyString> {
        Utf16FlyString string;
        if (!arg1.is_null())
            string = TRY(WebIDL::to_utf16_string(vm, arg1));
        return string;
    }(); }));

    auto arg2 = vm.argument(2);
    GC::Ptr<DOM::DocumentType> doctype = nullptr;
    if (!arg2.is_undefined())
        doctype = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<GC::Ptr<DOM::DocumentType>> {
        GC::Ptr<DOM::DocumentType> value;

        if (!arg2.is_nullish()) {

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

    [[maybe_unused]] auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->create_document(namespace_, qualified_name, doctype); }));
    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(DOMImplementationPrototype::create_html_document)
{
    WebIDL::log_trace(vm, "DOMImplementationPrototype::create_html_document");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] DOM::DOMImplementation* idl_object = TRY(impl_from(vm));

    auto arg0 = vm.argument(0);
    Optional<Utf16String> title {};
    if (!arg0.is_undefined())
        title = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<Utf16String> {
        return TRY(WebIDL::to_utf16_string(vm, arg0));
    }(); }));

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

JS_DEFINE_NATIVE_FUNCTION(DOMImplementationPrototype::has_feature)
{
    WebIDL::log_trace(vm, "DOMImplementationPrototype::has_feature");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] DOM::DOMImplementation* idl_object = TRY(impl_from(vm));

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

} // namespace Web::Bindings
