#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/TextMetrics.h>
#include <LibWeb/HTML/TextMetrics.h>
#include <LibWeb/WebIDL/Tracing.h>

namespace Web::Bindings {

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

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

void TextMetricsPrototype::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 width_id = "width"_utf16_fly_string;
    auto native_width_getter = JS::NativeFunction::create(realm, width_getter, 0, width_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_width_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto width_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(width_id, native_width_getter, native_width_setter, width_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 actual_bounding_box_left_id = "actualBoundingBoxLeft"_utf16_fly_string;
    auto native_actual_bounding_box_left_getter = JS::NativeFunction::create(realm, actual_bounding_box_left_getter, 0, actual_bounding_box_left_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_actual_bounding_box_left_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto actual_bounding_box_left_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(actual_bounding_box_left_id, native_actual_bounding_box_left_getter, native_actual_bounding_box_left_setter, actual_bounding_box_left_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 actual_bounding_box_right_id = "actualBoundingBoxRight"_utf16_fly_string;
    auto native_actual_bounding_box_right_getter = JS::NativeFunction::create(realm, actual_bounding_box_right_getter, 0, actual_bounding_box_right_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_actual_bounding_box_right_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto actual_bounding_box_right_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(actual_bounding_box_right_id, native_actual_bounding_box_right_getter, native_actual_bounding_box_right_setter, actual_bounding_box_right_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 font_bounding_box_ascent_id = "fontBoundingBoxAscent"_utf16_fly_string;
    auto native_font_bounding_box_ascent_getter = JS::NativeFunction::create(realm, font_bounding_box_ascent_getter, 0, font_bounding_box_ascent_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_font_bounding_box_ascent_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto font_bounding_box_ascent_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(font_bounding_box_ascent_id, native_font_bounding_box_ascent_getter, native_font_bounding_box_ascent_setter, font_bounding_box_ascent_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 font_bounding_box_descent_id = "fontBoundingBoxDescent"_utf16_fly_string;
    auto native_font_bounding_box_descent_getter = JS::NativeFunction::create(realm, font_bounding_box_descent_getter, 0, font_bounding_box_descent_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_font_bounding_box_descent_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto font_bounding_box_descent_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(font_bounding_box_descent_id, native_font_bounding_box_descent_getter, native_font_bounding_box_descent_setter, font_bounding_box_descent_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 actual_bounding_box_ascent_id = "actualBoundingBoxAscent"_utf16_fly_string;
    auto native_actual_bounding_box_ascent_getter = JS::NativeFunction::create(realm, actual_bounding_box_ascent_getter, 0, actual_bounding_box_ascent_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_actual_bounding_box_ascent_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto actual_bounding_box_ascent_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(actual_bounding_box_ascent_id, native_actual_bounding_box_ascent_getter, native_actual_bounding_box_ascent_setter, actual_bounding_box_ascent_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 actual_bounding_box_descent_id = "actualBoundingBoxDescent"_utf16_fly_string;
    auto native_actual_bounding_box_descent_getter = JS::NativeFunction::create(realm, actual_bounding_box_descent_getter, 0, actual_bounding_box_descent_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_actual_bounding_box_descent_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto actual_bounding_box_descent_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(actual_bounding_box_descent_id, native_actual_bounding_box_descent_getter, native_actual_bounding_box_descent_setter, actual_bounding_box_descent_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 em_height_ascent_id = "emHeightAscent"_utf16_fly_string;
    auto native_em_height_ascent_getter = JS::NativeFunction::create(realm, em_height_ascent_getter, 0, em_height_ascent_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_em_height_ascent_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto em_height_ascent_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(em_height_ascent_id, native_em_height_ascent_getter, native_em_height_ascent_setter, em_height_ascent_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 em_height_descent_id = "emHeightDescent"_utf16_fly_string;
    auto native_em_height_descent_getter = JS::NativeFunction::create(realm, em_height_descent_getter, 0, em_height_descent_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_em_height_descent_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto em_height_descent_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(em_height_descent_id, native_em_height_descent_getter, native_em_height_descent_setter, em_height_descent_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 hanging_baseline_id = "hangingBaseline"_utf16_fly_string;
    auto native_hanging_baseline_getter = JS::NativeFunction::create(realm, hanging_baseline_getter, 0, hanging_baseline_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_hanging_baseline_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto hanging_baseline_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(hanging_baseline_id, native_hanging_baseline_getter, native_hanging_baseline_setter, hanging_baseline_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 alphabetic_baseline_id = "alphabeticBaseline"_utf16_fly_string;
    auto native_alphabetic_baseline_getter = JS::NativeFunction::create(realm, alphabetic_baseline_getter, 0, alphabetic_baseline_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_alphabetic_baseline_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto alphabetic_baseline_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(alphabetic_baseline_id, native_alphabetic_baseline_getter, native_alphabetic_baseline_setter, alphabetic_baseline_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 ideographic_baseline_id = "ideographicBaseline"_utf16_fly_string;
    auto native_ideographic_baseline_getter = JS::NativeFunction::create(realm, ideographic_baseline_getter, 0, ideographic_baseline_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_ideographic_baseline_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto ideographic_baseline_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(ideographic_baseline_id, native_ideographic_baseline_getter, native_ideographic_baseline_setter, ideographic_baseline_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_direct_property(vm.well_known_symbol_to_string_tag(), JS::PrimitiveString::create(vm, "TextMetrics"_utf16), JS::Attribute::Configurable);
}

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

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

[[maybe_unused]] static JS::ThrowCompletionOr<HTML::TextMetrics*> 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(TextMetricsPrototype::width_getter)
{
    WebIDL::log_trace(vm, "TextMetricsPrototype::width_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->width(); }));

    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(TextMetricsPrototype::actual_bounding_box_left_getter)
{
    WebIDL::log_trace(vm, "TextMetricsPrototype::actual_bounding_box_left_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->actual_bounding_box_left(); }));

    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(TextMetricsPrototype::actual_bounding_box_right_getter)
{
    WebIDL::log_trace(vm, "TextMetricsPrototype::actual_bounding_box_right_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->actual_bounding_box_right(); }));

    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(TextMetricsPrototype::font_bounding_box_ascent_getter)
{
    WebIDL::log_trace(vm, "TextMetricsPrototype::font_bounding_box_ascent_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->font_bounding_box_ascent(); }));

    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(TextMetricsPrototype::font_bounding_box_descent_getter)
{
    WebIDL::log_trace(vm, "TextMetricsPrototype::font_bounding_box_descent_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->font_bounding_box_descent(); }));

    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(TextMetricsPrototype::actual_bounding_box_ascent_getter)
{
    WebIDL::log_trace(vm, "TextMetricsPrototype::actual_bounding_box_ascent_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->actual_bounding_box_ascent(); }));

    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(TextMetricsPrototype::actual_bounding_box_descent_getter)
{
    WebIDL::log_trace(vm, "TextMetricsPrototype::actual_bounding_box_descent_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->actual_bounding_box_descent(); }));

    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(TextMetricsPrototype::em_height_ascent_getter)
{
    WebIDL::log_trace(vm, "TextMetricsPrototype::em_height_ascent_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->em_height_ascent(); }));

    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(TextMetricsPrototype::em_height_descent_getter)
{
    WebIDL::log_trace(vm, "TextMetricsPrototype::em_height_descent_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->em_height_descent(); }));

    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(TextMetricsPrototype::hanging_baseline_getter)
{
    WebIDL::log_trace(vm, "TextMetricsPrototype::hanging_baseline_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->hanging_baseline(); }));

    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(TextMetricsPrototype::alphabetic_baseline_getter)
{
    WebIDL::log_trace(vm, "TextMetricsPrototype::alphabetic_baseline_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->alphabetic_baseline(); }));

    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(TextMetricsPrototype::ideographic_baseline_getter)
{
    WebIDL::log_trace(vm, "TextMetricsPrototype::ideographic_baseline_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->ideographic_baseline(); }));

    return JS::Value(R);
}

} // namespace Web::Bindings
