#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/Value.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/PerformanceEntry.h>
#include <LibWeb/PerformanceTimeline/PerformanceEntry.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
#include <LibWeb/WebIDL/Tracing.h>

namespace Web::Bindings {

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

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

void PerformanceEntryPrototype::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 name_id = "name"_utf16_fly_string;
    auto native_name_getter = JS::NativeFunction::create(realm, name_getter, 0, name_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_name_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto name_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(name_id, native_name_getter, native_name_setter, name_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 entry_type_id = "entryType"_utf16_fly_string;
    auto native_entry_type_getter = JS::NativeFunction::create(realm, entry_type_getter, 0, entry_type_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_entry_type_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto entry_type_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(entry_type_id, native_entry_type_getter, native_entry_type_setter, entry_type_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 start_time_id = "startTime"_utf16_fly_string;
    auto native_start_time_getter = JS::NativeFunction::create(realm, start_time_getter, 0, start_time_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_start_time_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto start_time_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(start_time_id, native_start_time_getter, native_start_time_setter, start_time_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 duration_id = "duration"_utf16_fly_string;
    auto native_duration_getter = JS::NativeFunction::create(realm, duration_getter, 0, duration_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_duration_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto duration_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(duration_id, native_duration_getter, native_duration_setter, duration_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, "toJSON"_utf16_fly_string, to_json, 0, default_attributes);

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

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

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

[[maybe_unused]] static JS::ThrowCompletionOr<PerformanceTimeline::PerformanceEntry*> 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(PerformanceEntryPrototype::name_getter)
{
    WebIDL::log_trace(vm, "PerformanceEntryPrototype::name_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->name(); }));

    return WebIDL::primitive_string_from_string(vm, R);
}

JS_DEFINE_NATIVE_FUNCTION(PerformanceEntryPrototype::entry_type_getter)
{
    WebIDL::log_trace(vm, "PerformanceEntryPrototype::entry_type_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->entry_type(); }));

    return WebIDL::primitive_string_from_string(vm, R);
}

JS_DEFINE_NATIVE_FUNCTION(PerformanceEntryPrototype::start_time_getter)
{
    WebIDL::log_trace(vm, "PerformanceEntryPrototype::start_time_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->start_time(); }));

    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(PerformanceEntryPrototype::duration_getter)
{
    WebIDL::log_trace(vm, "PerformanceEntryPrototype::duration_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->duration(); }));

    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(PerformanceEntryPrototype::to_json)
{
    WebIDL::log_trace(vm, "PerformanceEntryPrototype::to_json");
    auto& realm = *vm.current_realm();

    [[maybe_unused]] auto* idl_object = TRY(impl_from(vm));

    // 4. Let result be OrdinaryObjectCreate(%Object.prototype%).
    auto result = JS::Object::create(realm, realm.intrinsics().object_prototype());

    // 5. For each key → value of map:
    auto name_0_value = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->name(); }));

    // 1. Let k be key converted to a JavaScript value.
    auto name_0_key = "name"_utf16_fly_string;

    // 2. Let v be value converted to a JavaScript value.
    auto name_0_value_js = WebIDL::primitive_string_from_string(vm, name_0_value);

    // 3. Perform ! CreateDataPropertyOrThrow(result, k, v).
    MUST(result->create_data_property(name_0_key, name_0_value_js));
    auto entry_type_1_value = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->entry_type(); }));

    // 1. Let k be key converted to a JavaScript value.
    auto entry_type_1_key = "entryType"_utf16_fly_string;

    // 2. Let v be value converted to a JavaScript value.
    auto entry_type_1_value_js = WebIDL::primitive_string_from_string(vm, entry_type_1_value);

    // 3. Perform ! CreateDataPropertyOrThrow(result, k, v).
    MUST(result->create_data_property(entry_type_1_key, entry_type_1_value_js));
    auto start_time_2_value = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->start_time(); }));

    // 1. Let k be key converted to a JavaScript value.
    auto start_time_2_key = "startTime"_utf16_fly_string;

    // 2. Let v be value converted to a JavaScript value.
    auto start_time_2_value_js = JS::Value(start_time_2_value);

    // 3. Perform ! CreateDataPropertyOrThrow(result, k, v).
    MUST(result->create_data_property(start_time_2_key, start_time_2_value_js));
    auto duration_3_value = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->duration(); }));

    // 1. Let k be key converted to a JavaScript value.
    auto duration_3_key = "duration"_utf16_fly_string;

    // 2. Let v be value converted to a JavaScript value.
    auto duration_3_value_js = JS::Value(duration_3_value);

    // 3. Perform ! CreateDataPropertyOrThrow(result, k, v).
    MUST(result->create_data_property(duration_3_key, duration_3_value_js));

    // 6. Return result.
    return result;
}

} // namespace Web::Bindings
