#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/PropertyDescriptor.h>
#include <LibJS/Runtime/Value.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibWeb/Bindings/ANGLEInstancedArrays.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/WebGL/Extensions/ANGLEInstancedArrays.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
#include <LibWeb/WebIDL/Tracing.h>
#include <LibWeb/WebIDL/Types.h>

namespace Web::Bindings {

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

    // 1. FIXME: If const is not exposed in realm, then continue.
    // 2. Let value be the result of converting const’s IDL value to a JavaScript value.
    // 3. Let desc be the PropertyDescriptor{[[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false, [[Value]]: value}.
    // 4. Let id be const’s identifier.
    // 5. Perform ! DefinePropertyOrThrow(target, id, desc).
    object.define_direct_property("VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE"_utf16_fly_string, JS::Value(static_cast<WebIDL::UnsignedLong>(0x88FE)), JS::Attribute::Enumerable);
}

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

void ANGLEInstancedArraysPrototype::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, "drawArraysInstancedANGLE"_utf16_fly_string, draw_arrays_instanced_angle, 4, default_attributes);

    object.define_native_function(realm, "drawElementsInstancedANGLE"_utf16_fly_string, draw_elements_instanced_angle, 5, default_attributes);

    object.define_native_function(realm, "vertexAttribDivisorANGLE"_utf16_fly_string, vertex_attrib_divisor_angle, 2, default_attributes);


    // 1. FIXME: If const is not exposed in realm, then continue.
    // 2. Let value be the result of converting const’s IDL value to a JavaScript value.
    // 3. Let desc be the PropertyDescriptor{[[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false, [[Value]]: value}.
    // 4. Let id be const’s identifier.
    // 5. Perform ! DefinePropertyOrThrow(target, id, desc).
    object.define_direct_property("VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE"_utf16_fly_string, JS::Value(static_cast<WebIDL::UnsignedLong>(0x88FE)), JS::Attribute::Enumerable);
    object.define_direct_property(vm.well_known_symbol_to_string_tag(), JS::PrimitiveString::create(vm, "ANGLEInstancedArrays"_utf16), JS::Attribute::Configurable);
}

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

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

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

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

    auto arg0 = vm.argument(0);
    auto mode = TRY(throw_dom_exception_if_needed(vm, [&] { return WebIDL::convert_to_int<WebIDL::UnsignedLong>(vm, arg0, WebIDL::EnforceRange::No, WebIDL::Clamp::No); }));

    auto arg1 = vm.argument(1);
    auto first = TRY(throw_dom_exception_if_needed(vm, [&] { return WebIDL::convert_to_int<WebIDL::Long>(vm, arg1, WebIDL::EnforceRange::No, WebIDL::Clamp::No); }));

    auto arg2 = vm.argument(2);
    auto count = TRY(throw_dom_exception_if_needed(vm, [&] { return WebIDL::convert_to_int<WebIDL::Long>(vm, arg2, WebIDL::EnforceRange::No, WebIDL::Clamp::No); }));

    auto arg3 = vm.argument(3);
    auto primcount = TRY(throw_dom_exception_if_needed(vm, [&] { return WebIDL::convert_to_int<WebIDL::Long>(vm, arg3, WebIDL::EnforceRange::No, WebIDL::Clamp::No); }));

    [[maybe_unused]] auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->draw_arrays_instanced_angle(mode, first, count, primcount); }));
    return JS::js_undefined();
}

JS_DEFINE_NATIVE_FUNCTION(ANGLEInstancedArraysPrototype::draw_elements_instanced_angle)
{
    WebIDL::log_trace(vm, "ANGLEInstancedArraysPrototype::draw_elements_instanced_angle");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] WebGL::ANGLEInstancedArrays* idl_object = TRY(impl_from(vm));

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

    auto arg0 = vm.argument(0);
    auto mode = TRY(throw_dom_exception_if_needed(vm, [&] { return WebIDL::convert_to_int<WebIDL::UnsignedLong>(vm, arg0, WebIDL::EnforceRange::No, WebIDL::Clamp::No); }));

    auto arg1 = vm.argument(1);
    auto count = TRY(throw_dom_exception_if_needed(vm, [&] { return WebIDL::convert_to_int<WebIDL::Long>(vm, arg1, WebIDL::EnforceRange::No, WebIDL::Clamp::No); }));

    auto arg2 = vm.argument(2);
    auto type = TRY(throw_dom_exception_if_needed(vm, [&] { return WebIDL::convert_to_int<WebIDL::UnsignedLong>(vm, arg2, WebIDL::EnforceRange::No, WebIDL::Clamp::No); }));

    auto arg3 = vm.argument(3);
    auto offset = TRY(throw_dom_exception_if_needed(vm, [&] { return WebIDL::convert_to_int<WebIDL::LongLong>(vm, arg3, WebIDL::EnforceRange::No, WebIDL::Clamp::No); }));

    auto arg4 = vm.argument(4);
    auto primcount = TRY(throw_dom_exception_if_needed(vm, [&] { return WebIDL::convert_to_int<WebIDL::Long>(vm, arg4, WebIDL::EnforceRange::No, WebIDL::Clamp::No); }));

    [[maybe_unused]] auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->draw_elements_instanced_angle(mode, count, type, offset, primcount); }));
    return JS::js_undefined();
}

JS_DEFINE_NATIVE_FUNCTION(ANGLEInstancedArraysPrototype::vertex_attrib_divisor_angle)
{
    WebIDL::log_trace(vm, "ANGLEInstancedArraysPrototype::vertex_attrib_divisor_angle");
    [[maybe_unused]] auto& realm = *vm.current_realm();
    [[maybe_unused]] WebGL::ANGLEInstancedArrays* idl_object = TRY(impl_from(vm));

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

    auto arg0 = vm.argument(0);
    auto index = TRY(throw_dom_exception_if_needed(vm, [&] { return WebIDL::convert_to_int<WebIDL::UnsignedLong>(vm, arg0, WebIDL::EnforceRange::No, WebIDL::Clamp::No); }));

    auto arg1 = vm.argument(1);
    auto divisor = TRY(throw_dom_exception_if_needed(vm, [&] { return WebIDL::convert_to_int<WebIDL::UnsignedLong>(vm, arg1, WebIDL::EnforceRange::No, WebIDL::Clamp::No); }));

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

} // namespace Web::Bindings
