#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/GeolocationCoordinates.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Geolocation/GeolocationCoordinates.h>
#include <LibWeb/WebIDL/Tracing.h>

namespace Web::Bindings {

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

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

void GeolocationCoordinatesPrototype::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 accuracy_id = "accuracy"_utf16_fly_string;
    auto native_accuracy_getter = JS::NativeFunction::create(realm, accuracy_getter, 0, accuracy_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_accuracy_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto accuracy_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(accuracy_id, native_accuracy_getter, native_accuracy_setter, accuracy_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 latitude_id = "latitude"_utf16_fly_string;
    auto native_latitude_getter = JS::NativeFunction::create(realm, latitude_getter, 0, latitude_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_latitude_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto latitude_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(latitude_id, native_latitude_getter, native_latitude_setter, latitude_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 longitude_id = "longitude"_utf16_fly_string;
    auto native_longitude_getter = JS::NativeFunction::create(realm, longitude_getter, 0, longitude_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_longitude_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto longitude_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(longitude_id, native_longitude_getter, native_longitude_setter, longitude_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 altitude_id = "altitude"_utf16_fly_string;
    auto native_altitude_getter = JS::NativeFunction::create(realm, altitude_getter, 0, altitude_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_altitude_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto altitude_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(altitude_id, native_altitude_getter, native_altitude_setter, altitude_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 altitude_accuracy_id = "altitudeAccuracy"_utf16_fly_string;
    auto native_altitude_accuracy_getter = JS::NativeFunction::create(realm, altitude_accuracy_getter, 0, altitude_accuracy_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_altitude_accuracy_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto altitude_accuracy_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(altitude_accuracy_id, native_altitude_accuracy_getter, native_altitude_accuracy_setter, altitude_accuracy_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 heading_id = "heading"_utf16_fly_string;
    auto native_heading_getter = JS::NativeFunction::create(realm, heading_getter, 0, heading_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_heading_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto heading_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(heading_id, native_heading_getter, native_heading_setter, heading_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 speed_id = "speed"_utf16_fly_string;
    auto native_speed_getter = JS::NativeFunction::create(realm, speed_getter, 0, speed_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_speed_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto speed_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(speed_id, native_speed_getter, native_speed_setter, speed_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, "GeolocationCoordinates"_utf16), JS::Attribute::Configurable);
}

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

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

[[maybe_unused]] static JS::ThrowCompletionOr<Geolocation::GeolocationCoordinates*> 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(GeolocationCoordinatesPrototype::accuracy_getter)
{
    WebIDL::log_trace(vm, "GeolocationCoordinatesPrototype::accuracy_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->accuracy(); }));

    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(GeolocationCoordinatesPrototype::latitude_getter)
{
    WebIDL::log_trace(vm, "GeolocationCoordinatesPrototype::latitude_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->latitude(); }));

    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(GeolocationCoordinatesPrototype::longitude_getter)
{
    WebIDL::log_trace(vm, "GeolocationCoordinatesPrototype::longitude_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->longitude(); }));

    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(GeolocationCoordinatesPrototype::altitude_getter)
{
    WebIDL::log_trace(vm, "GeolocationCoordinatesPrototype::altitude_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->altitude(); }));

    return [&]() -> JS::Value {
        // 1. If the IDL nullable type T? value is null, then the JavaScript value is null.
        if (!R.has_value())
            return JS::js_null();

        // 2. Otherwise, the JavaScript value is the result of converting the IDL nullable type value to the inner IDL type T.
        return JS::Value(JS::Value(R.value()));
    }();
}

JS_DEFINE_NATIVE_FUNCTION(GeolocationCoordinatesPrototype::altitude_accuracy_getter)
{
    WebIDL::log_trace(vm, "GeolocationCoordinatesPrototype::altitude_accuracy_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->altitude_accuracy(); }));

    return [&]() -> JS::Value {
        // 1. If the IDL nullable type T? value is null, then the JavaScript value is null.
        if (!R.has_value())
            return JS::js_null();

        // 2. Otherwise, the JavaScript value is the result of converting the IDL nullable type value to the inner IDL type T.
        return JS::Value(JS::Value(R.value()));
    }();
}

JS_DEFINE_NATIVE_FUNCTION(GeolocationCoordinatesPrototype::heading_getter)
{
    WebIDL::log_trace(vm, "GeolocationCoordinatesPrototype::heading_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->heading(); }));

    return [&]() -> JS::Value {
        // 1. If the IDL nullable type T? value is null, then the JavaScript value is null.
        if (!R.has_value())
            return JS::js_null();

        // 2. Otherwise, the JavaScript value is the result of converting the IDL nullable type value to the inner IDL type T.
        return JS::Value(JS::Value(R.value()));
    }();
}

JS_DEFINE_NATIVE_FUNCTION(GeolocationCoordinatesPrototype::speed_getter)
{
    WebIDL::log_trace(vm, "GeolocationCoordinatesPrototype::speed_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->speed(); }));

    return [&]() -> JS::Value {
        // 1. If the IDL nullable type T? value is null, then the JavaScript value is null.
        if (!R.has_value())
            return JS::js_null();

        // 2. Otherwise, the JavaScript value is the result of converting the IDL nullable type value to the inner IDL type T.
        return JS::Value(JS::Value(R.value()));
    }();
}

JS_DEFINE_NATIVE_FUNCTION(GeolocationCoordinatesPrototype::to_json)
{
    WebIDL::log_trace(vm, "GeolocationCoordinatesPrototype::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 accuracy_0_value = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->accuracy(); }));

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

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

    // 3. Perform ! CreateDataPropertyOrThrow(result, k, v).
    MUST(result->create_data_property(accuracy_0_key, accuracy_0_value_js));
    auto latitude_1_value = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->latitude(); }));

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

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

    // 3. Perform ! CreateDataPropertyOrThrow(result, k, v).
    MUST(result->create_data_property(latitude_1_key, latitude_1_value_js));
    auto longitude_2_value = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->longitude(); }));

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

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

    // 3. Perform ! CreateDataPropertyOrThrow(result, k, v).
    MUST(result->create_data_property(longitude_2_key, longitude_2_value_js));
    auto altitude_3_value = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->altitude(); }));

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

    // 2. Let v be value converted to a JavaScript value.
    auto altitude_3_value_js = [&]() -> JS::Value {
        // 1. If the IDL nullable type T? value is null, then the JavaScript value is null.
        if (!altitude_3_value.has_value())
            return JS::js_null();

        // 2. Otherwise, the JavaScript value is the result of converting the IDL nullable type value to the inner IDL type T.
        return JS::Value(JS::Value(altitude_3_value.value()));
    }();

    // 3. Perform ! CreateDataPropertyOrThrow(result, k, v).
    MUST(result->create_data_property(altitude_3_key, altitude_3_value_js));
    auto altitude_accuracy_4_value = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->altitude_accuracy(); }));

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

    // 2. Let v be value converted to a JavaScript value.
    auto altitude_accuracy_4_value_js = [&]() -> JS::Value {
        // 1. If the IDL nullable type T? value is null, then the JavaScript value is null.
        if (!altitude_accuracy_4_value.has_value())
            return JS::js_null();

        // 2. Otherwise, the JavaScript value is the result of converting the IDL nullable type value to the inner IDL type T.
        return JS::Value(JS::Value(altitude_accuracy_4_value.value()));
    }();

    // 3. Perform ! CreateDataPropertyOrThrow(result, k, v).
    MUST(result->create_data_property(altitude_accuracy_4_key, altitude_accuracy_4_value_js));
    auto heading_5_value = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->heading(); }));

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

    // 2. Let v be value converted to a JavaScript value.
    auto heading_5_value_js = [&]() -> JS::Value {
        // 1. If the IDL nullable type T? value is null, then the JavaScript value is null.
        if (!heading_5_value.has_value())
            return JS::js_null();

        // 2. Otherwise, the JavaScript value is the result of converting the IDL nullable type value to the inner IDL type T.
        return JS::Value(JS::Value(heading_5_value.value()));
    }();

    // 3. Perform ! CreateDataPropertyOrThrow(result, k, v).
    MUST(result->create_data_property(heading_5_key, heading_5_value_js));
    auto speed_6_value = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->speed(); }));

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

    // 2. Let v be value converted to a JavaScript value.
    auto speed_6_value_js = [&]() -> JS::Value {
        // 1. If the IDL nullable type T? value is null, then the JavaScript value is null.
        if (!speed_6_value.has_value())
            return JS::js_null();

        // 2. Otherwise, the JavaScript value is the result of converting the IDL nullable type value to the inner IDL type T.
        return JS::Value(JS::Value(speed_6_value.value()));
    }();

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

    // 6. Return result.
    return result;
}

} // namespace Web::Bindings
