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

namespace Web::Bindings {

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

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

void GeolocationPositionPrototype::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 coords_id = "coords"_utf16_fly_string;
    auto native_coords_getter = JS::NativeFunction::create(realm, coords_getter, 0, coords_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_coords_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto coords_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(coords_id, native_coords_getter, native_coords_setter, coords_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 timestamp_id = "timestamp"_utf16_fly_string;
    auto native_timestamp_getter = JS::NativeFunction::create(realm, timestamp_getter, 0, timestamp_id, &realm, "get"sv);
    GC::Ptr<JS::NativeFunction> native_timestamp_setter;

    // 4. Let configurable be false if attr is unforgeable and true otherwise.
    auto timestamp_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(timestamp_id, native_timestamp_getter, native_timestamp_setter, timestamp_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, "GeolocationPosition"_utf16), JS::Attribute::Configurable);
}

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

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

[[maybe_unused]] static JS::ThrowCompletionOr<Geolocation::GeolocationPosition*> 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(GeolocationPositionPrototype::coords_getter)
{
    WebIDL::log_trace(vm, "GeolocationPositionPrototype::coords_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->coords(); }));

    return JS::Value(R);
}

JS_DEFINE_NATIVE_FUNCTION(GeolocationPositionPrototype::timestamp_getter)
{
    WebIDL::log_trace(vm, "GeolocationPositionPrototype::timestamp_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->timestamp(); }));

    return JS::Value(static_cast<double>(R));
}

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

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

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

    // 3. Perform ! CreateDataPropertyOrThrow(result, k, v).
    MUST(result->create_data_property(coords_0_key, coords_0_value_js));
    auto timestamp_1_value = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->timestamp(); }));

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

    // 2. Let v be value converted to a JavaScript value.
    auto timestamp_1_value_js = JS::Value(static_cast<double>(timestamp_1_value));

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

    // 6. Return result.
    return result;
}

} // namespace Web::Bindings
