#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/Set.h>
#include <LibJS/Runtime/SetIterator.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibWeb/Bindings/CustomStateSet.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/HTML/CustomElements/CustomStateSet.h>
#include <LibWeb/WebIDL/Tracing.h>

namespace Web::Bindings {

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

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

GC_DEFINE_ALLOCATOR(CustomStateSetPrototype);

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

CustomStateSetPrototype::~CustomStateSetPrototype()
{
}


void CustomStateSetPrototype::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_accessor(realm, vm.names.size, get_size, nullptr, JS::Attribute::Enumerable | JS::Attribute::Configurable);
    object.define_native_function(realm, vm.names.entries, entries, 0, default_attributes);
    object.define_native_function(realm, vm.names.keys, values, 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.values), JS::Attribute::Configurable | JS::Attribute::Writable);
    object.define_native_function(realm, vm.names.forEach, for_each, 1, default_attributes);
    object.define_native_function(realm, vm.names.has, has, 1, default_attributes);
    object.define_native_function(realm, vm.names.add, add, 1, default_attributes);
    object.define_native_function(realm, vm.names.delete_, delete_, 1, default_attributes);
    object.define_native_function(realm, vm.names.clear, clear, 0, default_attributes);

    object.define_direct_property(vm.well_known_symbol_to_string_tag(), JS::PrimitiveString::create(vm, "CustomStateSet"_utf16), JS::Attribute::Configurable);
    Base::initialize(realm);
}

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

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

[[maybe_unused]] static JS::ThrowCompletionOr<HTML::CustomStateSet*> 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);
}

// https://webidl.spec.whatwg.org/#js-set-size
JS_DEFINE_NATIVE_FUNCTION(CustomStateSetPrototype::get_size)
{
    WebIDL::log_trace(vm, "CustomStateSetPrototype::size");

    // 1. Let O be the this value, implementation-checked against A with identifier "size" and type "getter".
    auto* this_impl = TRY(impl_from(vm));

    // 2. Let set be the set entries of the IDL value that represents a reference to O.
    GC::Ref<JS::Set> set = this_impl->set_entries();

    // 3. Return set’s size, converted to a JavaScript value.
    return set->set_size();
}

// https://webidl.spec.whatwg.org/#js-set-entries
JS_DEFINE_NATIVE_FUNCTION(CustomStateSetPrototype::entries)
{
    WebIDL::log_trace(vm, "CustomStateSetPrototype::values");
    auto& realm = *vm.current_realm();

    // 1. Let O be the this value, implementation-checked against A with identifier "entries" and type "method".
    auto* this_impl = TRY(impl_from(vm));

    // 2. Let set be the set entries of the IDL value that represents a reference to O.
    GC::Ref<JS::Set> set = this_impl->set_entries();

    // 3. Return the result of creating a set iterator from set with kind "key+value".
    return JS::SetIterator::create(realm, *set, PropertyKind::KeyAndValue);
}

// https://webidl.spec.whatwg.org/#js-set-values
JS_DEFINE_NATIVE_FUNCTION(CustomStateSetPrototype::values)
{
    WebIDL::log_trace(vm, "CustomStateSetPrototype::values");
    auto& realm = *vm.current_realm();

    // 1. Let O be the this value, implementation-checked against A with identifier "values" and type "method".
    auto* this_impl = TRY(impl_from(vm));

    // 2. Let set be the set entries of the IDL value that represents a reference to O.
    GC::Ref<JS::Set> set = this_impl->set_entries();

    // 3. Return the result of creating a set iterator from set with kind "value".
    return JS::SetIterator::create(realm, *set, PropertyKind::Value);
}

// https://webidl.spec.whatwg.org/#js-set-forEach
JS_DEFINE_NATIVE_FUNCTION(CustomStateSetPrototype::for_each)
{
    WebIDL::log_trace(vm, "CustomStateSetPrototype::for_each");

    // 1. Let O be the this value, implementation-checked against A with identifier "forEach" and type "method".
    auto* this_impl = TRY(impl_from(vm));

    // 2. Let set be the set entries of the IDL value that represents a reference to O.
    GC::Ref<JS::Set> set = this_impl->set_entries();

    // 3. Let callbackFn be the first argument passed to the function, or undefined if not supplied.
    // 4. If IsCallable(callbackFn) is false, throw a TypeError.
    auto callback = vm.argument(0);
    if (!callback.is_function())
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAFunction, callback);

    // 5. Let thisArg be the second argument passed to the function, or undefined if not supplied.
    auto this_arg = vm.argument(1);

    // 6. For each value of set:
    for (auto value : *set) {
        // 1. Let jsValue be value converted to a JavaScript value.

        // 2. Perform ? Call(callbackFn, thisArg, « jsValue, jsValue, O»).
        TRY(JS::call(vm, callback.as_function(), this_arg, value, value, this_impl));
    }

    // 7. Return undefined.
    return JS::js_undefined();
}

// https://webidl.spec.whatwg.org/#js-set-has
JS_DEFINE_NATIVE_FUNCTION(CustomStateSetPrototype::has)
{
    WebIDL::log_trace(vm, "CustomStateSetPrototype::has");

    // 1. Let O be the this value, implementation-checked against A with identifier "has" and type "method".
    auto* this_impl = TRY(impl_from(vm));

    // 2. Let set be the set entries of the IDL value that represents a reference to O.
    GC::Ref<JS::Set> set = this_impl->set_entries();

    // 3. Let valueType be the value type specified in the setlike declaration.
    // 4. Let valueArg be the first argument passed to this function, or undefined if not supplied.
    // 5. Let value be valueArg converted to an IDL value of type valueType.
    // FIXME: 6. If value is -0, set value to +0.
    auto value = vm.argument(0);
        if (!value.is_string())
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "String");


    // 7. If set contains value, then return true, otherwise return false.
    return set->set_has(value);
}

// https://webidl.spec.whatwg.org/#js-set-add
JS_DEFINE_NATIVE_FUNCTION(CustomStateSetPrototype::add)
{
    WebIDL::log_trace(vm, "CustomStateSetPrototype::add");

    // 1. Let O be the this value, implementation-checked against A with identifier "add" and type "method".
    auto* this_impl = TRY(impl_from(vm));

    // 2. Let set be the set entries of the IDL value that represents a reference to O.
    GC::Ref<JS::Set> set = this_impl->set_entries();

    // 3. Let valueType be the value type specified in the setlike declaration.
    // 4. Let valueArg be the first argument passed to this function, or undefined if not supplied.
    // 5. Let value be valueArg converted to an IDL value of type valueType.
    // FIXME: 6. If value is -0, set value to +0.
    auto value = vm.argument(0);
        if (!value.is_string())
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "String");


    // 6. Append value to set.
    set->set_add(value);
    this_impl->on_set_modified_from_js({});

    return this_impl;
}

// https://webidl.spec.whatwg.org/#js-set-delete
JS_DEFINE_NATIVE_FUNCTION(CustomStateSetPrototype::delete_)
{
    WebIDL::log_trace(vm, "CustomStateSetPrototype::delete_");

    // 1. Let O be the this value, implementation-checked against A with identifier "delete" and type "method".
    auto* this_impl = TRY(impl_from(vm));

    // 2. Let set be O’s set entries.
    GC::Ref<JS::Set> set = this_impl->set_entries();

    // 3. Let valueType be the value type specified in the setlike declaration.
    // 4. Let valueArg be the first argument passed to this function, or undefined if not supplied.
    // 5. Let value be valueArg converted to an IDL value of type valueType.
    // 6. FIXME: If value is -0, set value to +0.
    auto value = vm.argument(0);
        if (!value.is_string())
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "String");


    // 7. Let retVal be true if set contains value, or else false.
    // 8. Remove value from set.
    auto result = set->set_remove(value);
    this_impl->on_set_modified_from_js({});

    // 9. Return retVal.
    return result;
}

// https://webidl.spec.whatwg.org/#js-set-clear
JS_DEFINE_NATIVE_FUNCTION(CustomStateSetPrototype::clear)
{
    WebIDL::log_trace(vm, "CustomStateSetPrototype::clear");

    // 1. Let O be the this value, implementation-checked against A with identifier "clear" and type "method".
    auto* this_impl = TRY(impl_from(vm));

    // 2. Let set be the set entries of the IDL value that represents a reference to O.
    GC::Ref<JS::Set> set = this_impl->set_entries();

    // 3. Empty set.
    // NOTE: Note: The set is preserved because there may be existing iterators, currently suspended, iterating over it.
    set->set_clear();
    this_impl->on_set_modified_from_js({});

    // 4. Return undefined.
    return JS::js_undefined();
}

} // namespace Web::Bindings
