#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/Value.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/IDBKeyRange.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/IndexedDB/IDBKeyRange.h>
#include <LibWeb/WebIDL/Tracing.h>

namespace Web::Bindings {

void IDBKeyRangeConstructor::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, "IDBKeyRange"_utf16), JS::Attribute::Configurable);
    object.define_direct_property(vm.names.prototype, &ensure_web_prototype<IDBKeyRangePrototype>(realm, "IDBKeyRange"_fly_string), 0);
    object.define_native_function(realm, "only"_utf16_fly_string, only, 1, JS::Attribute::Enumerable | JS::Attribute::Configurable | JS::Attribute::Writable);

    object.define_native_function(realm, "lowerBound"_utf16_fly_string, lower_bound, 1, JS::Attribute::Enumerable | JS::Attribute::Configurable | JS::Attribute::Writable);

    object.define_native_function(realm, "upperBound"_utf16_fly_string, upper_bound, 1, JS::Attribute::Enumerable | JS::Attribute::Configurable | JS::Attribute::Writable);

    object.define_native_function(realm, "bound"_utf16_fly_string, bound, 2, JS::Attribute::Enumerable | JS::Attribute::Configurable | JS::Attribute::Writable);

}

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

void IDBKeyRangePrototype::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());

    auto lower_id = "lower"_utf16_fly_string;
    auto native_lower_getter = JS::NativeFunction::create(realm, lower_getter, 0, lower_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_lower_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto lower_attributes = default_attributes;

    // 5. Let desc be the PropertyDescriptor{[[Get]]: getter, [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: configurable}.

    // 7. Perform ! DefinePropertyOrThrow(target, id, desc).
    object.define_direct_accessor(lower_id, native_lower_getter, native_lower_setter, lower_attributes);

    // 8. FIXME: If attr’s type is an observable array type with type argument T, then set target’s backing observable array exotic object for attr to the result of creating an observable array exotic object in realm, given T, attr’s set an indexed value algorithm, and attr’s delete an indexed value algorithm.
    auto upper_id = "upper"_utf16_fly_string;
    auto native_upper_getter = JS::NativeFunction::create(realm, upper_getter, 0, upper_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_upper_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto upper_attributes = default_attributes;

    // 5. Let desc be the PropertyDescriptor{[[Get]]: getter, [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: configurable}.

    // 7. Perform ! DefinePropertyOrThrow(target, id, desc).
    object.define_direct_accessor(upper_id, native_upper_getter, native_upper_setter, upper_attributes);

    // 8. FIXME: If attr’s type is an observable array type with type argument T, then set target’s backing observable array exotic object for attr to the result of creating an observable array exotic object in realm, given T, attr’s set an indexed value algorithm, and attr’s delete an indexed value algorithm.
    auto lower_open_id = "lowerOpen"_utf16_fly_string;
    auto native_lower_open_getter = JS::NativeFunction::create(realm, lower_open_getter, 0, lower_open_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_lower_open_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto lower_open_attributes = default_attributes;

    // 5. Let desc be the PropertyDescriptor{[[Get]]: getter, [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: configurable}.

    // 7. Perform ! DefinePropertyOrThrow(target, id, desc).
    object.define_direct_accessor(lower_open_id, native_lower_open_getter, native_lower_open_setter, lower_open_attributes);

    // 8. FIXME: If attr’s type is an observable array type with type argument T, then set target’s backing observable array exotic object for attr to the result of creating an observable array exotic object in realm, given T, attr’s set an indexed value algorithm, and attr’s delete an indexed value algorithm.
    auto upper_open_id = "upperOpen"_utf16_fly_string;
    auto native_upper_open_getter = JS::NativeFunction::create(realm, upper_open_getter, 0, upper_open_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_upper_open_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto upper_open_attributes = default_attributes;

    // 5. Let desc be the PropertyDescriptor{[[Get]]: getter, [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: configurable}.

    // 7. Perform ! DefinePropertyOrThrow(target, id, desc).
    object.define_direct_accessor(upper_open_id, native_upper_open_getter, native_upper_open_setter, upper_open_attributes);

    // 8. FIXME: If attr’s type is an observable array type with type argument T, then set target’s backing observable array exotic object for attr to the result of creating an observable array exotic object in realm, given T, attr’s set an indexed value algorithm, and attr’s delete an indexed value algorithm.
    object.define_native_function(realm, "includes"_utf16_fly_string, includes, 1, default_attributes);

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

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

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

[[maybe_unused]] static JS::ThrowCompletionOr<IndexedDB::IDBKeyRange*> 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(IDBKeyRangeConstructor::only)
{
    WebIDL::log_trace(vm, "IDBKeyRangeConstructor::only");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    if (vm.argument_count() < 1)
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::BadArgCountOne, "only");

    auto arg0 = vm.argument(0);
    auto value = TRY(throw_dom_exception_if_needed(vm, [&] { return arg0; }));

    [[maybe_unused]] auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return IndexedDB::IDBKeyRange::only(vm, value); }));
    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(IDBKeyRangeConstructor::lower_bound)
{
    WebIDL::log_trace(vm, "IDBKeyRangeConstructor::lower_bound");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    if (vm.argument_count() < 1)
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::BadArgCountOne, "lowerBound");

    auto arg0 = vm.argument(0);
    auto lower = TRY(throw_dom_exception_if_needed(vm, [&] { return arg0; }));

    auto arg1 = vm.argument(1);
    bool open = false;
    if (!arg1.is_undefined())
        open = TRY(throw_dom_exception_if_needed(vm, [&] { return arg1.to_boolean(); }));

    [[maybe_unused]] auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return IndexedDB::IDBKeyRange::lower_bound(vm, lower, open); }));
    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(IDBKeyRangeConstructor::upper_bound)
{
    WebIDL::log_trace(vm, "IDBKeyRangeConstructor::upper_bound");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    if (vm.argument_count() < 1)
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::BadArgCountOne, "upperBound");

    auto arg0 = vm.argument(0);
    auto upper = TRY(throw_dom_exception_if_needed(vm, [&] { return arg0; }));

    auto arg1 = vm.argument(1);
    bool open = false;
    if (!arg1.is_undefined())
        open = TRY(throw_dom_exception_if_needed(vm, [&] { return arg1.to_boolean(); }));

    [[maybe_unused]] auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return IndexedDB::IDBKeyRange::upper_bound(vm, upper, open); }));
    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(IDBKeyRangeConstructor::bound)
{
    WebIDL::log_trace(vm, "IDBKeyRangeConstructor::bound");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    if (vm.argument_count() < 2)
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::BadArgCountMany, "bound", "2");

    auto arg0 = vm.argument(0);
    auto lower = TRY(throw_dom_exception_if_needed(vm, [&] { return arg0; }));

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

    auto arg2 = vm.argument(2);
    bool lower_open = false;
    if (!arg2.is_undefined())
        lower_open = TRY(throw_dom_exception_if_needed(vm, [&] { return arg2.to_boolean(); }));

    auto arg3 = vm.argument(3);
    bool upper_open = false;
    if (!arg3.is_undefined())
        upper_open = TRY(throw_dom_exception_if_needed(vm, [&] { return arg3.to_boolean(); }));

    [[maybe_unused]] auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return IndexedDB::IDBKeyRange::bound(vm, lower, upper, lower_open, upper_open); }));
    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(IDBKeyRangePrototype::lower_getter)
{
    WebIDL::log_trace(vm, "IDBKeyRangePrototype::lower_getter");
    [[maybe_unused]] auto& realm = *vm.current_realm();

    auto* idl_object = TRY(impl_from(vm));


    auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->lower(); }));

    return R;
}

JS_DEFINE_NATIVE_FUNCTION(IDBKeyRangePrototype::upper_getter)
{
    WebIDL::log_trace(vm, "IDBKeyRangePrototype::upper_getter");
    [[maybe_unused]] auto& realm = *vm.current_realm();

    auto* idl_object = TRY(impl_from(vm));


    auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->upper(); }));

    return R;
}

JS_DEFINE_NATIVE_FUNCTION(IDBKeyRangePrototype::lower_open_getter)
{
    WebIDL::log_trace(vm, "IDBKeyRangePrototype::lower_open_getter");
    [[maybe_unused]] auto& realm = *vm.current_realm();

    auto* idl_object = TRY(impl_from(vm));


    auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->lower_open(); }));

    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(IDBKeyRangePrototype::upper_open_getter)
{
    WebIDL::log_trace(vm, "IDBKeyRangePrototype::upper_open_getter");
    [[maybe_unused]] auto& realm = *vm.current_realm();

    auto* idl_object = TRY(impl_from(vm));


    auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->upper_open(); }));

    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(IDBKeyRangePrototype::includes)
{
    WebIDL::log_trace(vm, "IDBKeyRangePrototype::includes");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] IndexedDB::IDBKeyRange* idl_object = TRY(impl_from(vm));

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

    auto arg0 = vm.argument(0);
    auto key = TRY(throw_dom_exception_if_needed(vm, [&] { return arg0; }));

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

} // namespace Web::Bindings
