#include <AK/String.h>
#include <AK/Utf16String.h>
#include <AK/Variant.h>
#include <LibGC/ConservativeVector.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/Value.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Bindings/StylePropertyMap.h>
#include <LibWeb/Bindings/StylePropertyMapReadOnly.h>
#include <LibWeb/CSS/CSSStyleValue.h>
#include <LibWeb/CSS/StylePropertyMap.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
#include <LibWeb/WebIDL/Tracing.h>

namespace Web::Bindings {

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

    object.set_prototype(&ensure_web_constructor<StylePropertyMapReadOnlyPrototype>(realm, "StylePropertyMapReadOnly"_fly_string));
    object.define_direct_property(vm.names.length, JS::Value(0), JS::Attribute::Configurable);
    object.define_direct_property(vm.names.name, JS::PrimitiveString::create(vm, "StylePropertyMap"_utf16), JS::Attribute::Configurable);
    object.define_direct_property(vm.names.prototype, &ensure_web_prototype<StylePropertyMapPrototype>(realm, "StylePropertyMap"_fly_string), 0);
}

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

void StylePropertyMapPrototype::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(GC::Ref { ensure_web_prototype<StylePropertyMapReadOnlyPrototype>(realm, "StylePropertyMapReadOnly"_fly_string) });
    object.define_native_function(realm, "set"_utf16_fly_string, set, 1, default_attributes);

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

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

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

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

void StylePropertyMapPrototype::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<CSS::StylePropertyMap*> impl_from(JS::VM& vm, JS::Value js_value)
{

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

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

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

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

    GC::ConservativeVector<Variant<GC::Ref<CSS::CSSStyleValue>, String>> values;
    if (vm.argument_count() > 1) {
        values.ensure_capacity(vm.argument_count() - 1);
        for (size_t i = 1; i < vm.argument_count(); ++i) {
            auto argument = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<Variant<GC::Ref<CSS::CSSStyleValue>, String>> {

        if (vm.argument(i).is_object()) {
            [[maybe_unused]] auto& object = vm.argument(i).as_object();

            if (is<PlatformObject>(object)) {

                if (auto* result = as_if<CSS::CSSStyleValue>(object))
                    return Variant<GC::Ref<CSS::CSSStyleValue>, String> { GC::Ref { *result } };
            }
        }

        auto values_string = TRY([&]() -> JS::ThrowCompletionOr<String> {
        return TRY(WebIDL::to_usv_string(vm, vm.argument(i)));
    }());
        return Variant<GC::Ref<CSS::CSSStyleValue>, String> { values_string };

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

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

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

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

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

    GC::ConservativeVector<Variant<GC::Ref<CSS::CSSStyleValue>, String>> values;
    if (vm.argument_count() > 1) {
        values.ensure_capacity(vm.argument_count() - 1);
        for (size_t i = 1; i < vm.argument_count(); ++i) {
            auto argument = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<Variant<GC::Ref<CSS::CSSStyleValue>, String>> {

        if (vm.argument(i).is_object()) {
            [[maybe_unused]] auto& object = vm.argument(i).as_object();

            if (is<PlatformObject>(object)) {

                if (auto* result = as_if<CSS::CSSStyleValue>(object))
                    return Variant<GC::Ref<CSS::CSSStyleValue>, String> { GC::Ref { *result } };
            }
        }

        auto values_string = TRY([&]() -> JS::ThrowCompletionOr<String> {
        return TRY(WebIDL::to_usv_string(vm, vm.argument(i)));
    }());
        return Variant<GC::Ref<CSS::CSSStyleValue>, String> { values_string };

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

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

JS_DEFINE_NATIVE_FUNCTION(StylePropertyMapPrototype::delete_)
{
    WebIDL::log_trace(vm, "StylePropertyMapPrototype::delete_");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] CSS::StylePropertyMap* 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 property = 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_(property); }));
    return JS::js_undefined();
}

JS_DEFINE_NATIVE_FUNCTION(StylePropertyMapPrototype::clear)
{
    WebIDL::log_trace(vm, "StylePropertyMapPrototype::clear");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] CSS::StylePropertyMap* idl_object = TRY(impl_from(vm));

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

} // namespace Web::Bindings
