#include <AK/String.h>
#include <AK/Variant.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/Iterator.h>
#include <LibJS/Runtime/Map.h>
#include <LibJS/Runtime/MapIterator.h>
#include <LibJS/Runtime/Value.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibWeb/Bindings/CSSFontFeatureValuesMap.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/CSS/CSSFontFeatureValuesMap.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
#include <LibWeb/WebIDL/Tracing.h>
#include <LibWeb/WebIDL/Types.h>

namespace Web::Bindings {

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

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

GC_DEFINE_ALLOCATOR(CSSFontFeatureValuesMapPrototype);

CSSFontFeatureValuesMapPrototype::CSSFontFeatureValuesMapPrototype([[maybe_unused]] JS::Realm& realm)
    : Object(ConstructWithPrototypeTag::Tag, realm.intrinsics().object_prototype())
{
}

CSSFontFeatureValuesMapPrototype::~CSSFontFeatureValuesMapPrototype()
{
}


void CSSFontFeatureValuesMapPrototype::initialize(JS::Realm& realm)
{
    auto& object = *this;
    [[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, "set"_utf16_fly_string, set, 2, default_attributes);

    object.define_native_accessor(realm, vm.names.size, get_size, nullptr, JS::Attribute::Enumerable | JS::Attribute::Configurable);
    object.define_native_function(realm, vm.names.entries, entries, 0, default_attributes);
    object.define_direct_property(vm.well_known_symbol_iterator(), object.get_without_side_effects(vm.names.entries), JS::Attribute::Configurable | JS::Attribute::Writable);
    object.define_native_function(realm, vm.names.keys, keys, 0, default_attributes);
    object.define_native_function(realm, vm.names.values, values, 0, default_attributes);
    object.define_native_function(realm, vm.names.forEach, for_each, 1, default_attributes);
    object.define_native_function(realm, vm.names.get, get, 1, default_attributes);
    object.define_native_function(realm, vm.names.has, has, 1, default_attributes);
    object.define_native_function(realm, vm.names.delete_, delete_, 1, default_attributes);
    object.define_native_function(realm, vm.names.clear, clear, 0, default_attributes);

    object.define_direct_property(vm.well_known_symbol_to_string_tag(), JS::PrimitiveString::create(vm, "CSSFontFeatureValuesMap"_utf16), JS::Attribute::Configurable);
    Base::initialize(realm);
}

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

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

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

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

    auto arg0 = vm.argument(0);
    auto feature_value_name = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<String> {
        return TRY(WebIDL::to_string(vm, arg0));
    }(); }));

    auto arg1 = vm.argument(1);
    auto values = TRY(throw_dom_exception_if_needed(vm, [&] { return [&]() -> JS::ThrowCompletionOr<Variant<WebIDL::UnsignedLong, Vector<WebIDL::UnsignedLong>>> {

        if (arg1.is_object()) {
            [[maybe_unused]] auto& object = arg1.as_object();

            // 1. Let method be ? GetMethod(V, @@iterator).
            auto method = TRY(arg1.get_method(vm, vm.well_known_symbol_iterator()));

            // 2. If method is not undefined, return the result of creating a sequence of that type from V and method.
            if (method) {
                auto sequence_union_type = TRY([&]() -> JS::ThrowCompletionOr<Vector<WebIDL::UnsignedLong>> {
        // To create an IDL value of type sequence<T> given an iterable iterable and an iterator getter method, perform the following steps:
        // 1. Let iteratorRecord be ? GetIteratorFromMethod(iterable, method).
        auto iterator = TRY(JS::get_iterator_from_method(vm, arg1, *method));

        Vector<WebIDL::UnsignedLong> sequence;

        // 2. Initialize i to be 0.
        // 3. Repeat
        for (;;) {
            // 1. Let next be ? IteratorStepValue(iteratorRecord).
            auto next = TRY(JS::iterator_step_value(vm, iterator));

            // 2. If next is done, then return an IDL sequence value of type sequence<T> of length i, where the value of the element at index j is Sj.
            if (!next.has_value())
                break;

            // 3. Initialize Si to the result of converting next to an IDL value of type T.
            auto next_value = next.release_value();
            auto sequence_item = TRY(throw_dom_exception_if_needed(vm, [&] { return WebIDL::convert_to_int<WebIDL::UnsignedLong>(vm, next_value, WebIDL::EnforceRange::No, WebIDL::Clamp::No); }));

            // 4. Set i to i + 1.
            sequence.append(sequence_item);
        }

        return sequence;
    }());
                return Variant<WebIDL::UnsignedLong, Vector<WebIDL::UnsignedLong>> { sequence_union_type };
            }

        }

        if (arg1.is_number()) {
            auto values_number = TRY(WebIDL::convert_to_int<WebIDL::UnsignedLong>(vm, arg1, WebIDL::EnforceRange::No, WebIDL::Clamp::No));
            return Variant<WebIDL::UnsignedLong, Vector<WebIDL::UnsignedLong>> { values_number };
        }

        auto values_number_fallback = TRY(WebIDL::convert_to_int<WebIDL::UnsignedLong>(vm, arg1, WebIDL::EnforceRange::No, WebIDL::Clamp::No));
        return Variant<WebIDL::UnsignedLong, Vector<WebIDL::UnsignedLong>> { values_number_fallback };

        return vm.throw_completion<JS::TypeError>("No union types matched"_utf16);
    }(); }));

    [[maybe_unused]] auto R = TRY(throw_dom_exception_if_needed(vm, [&] { return idl_object->set(feature_value_name, values); }));
    return JS::js_undefined();
}

// https://webidl.spec.whatwg.org/#js-map-size
JS_DEFINE_NATIVE_FUNCTION(CSSFontFeatureValuesMapPrototype::get_size)
{
    WebIDL::log_trace(vm, "CSSFontFeatureValuesMapPrototype::get_size");

    // 1. Let O be the this value, implementation-checked against A with identifier "size" and type "getter".
    auto* this_impl = TRY(impl_from(vm));

    // 2. Let map be the map entries of the IDL value that represents a reference to O.
    GC::Ref<JS::Map> map = this_impl->map_entries();

    // 3. Return map’s size, converted to a JavaScript value.
    return map->map_size();
}

// https://webidl.spec.whatwg.org/#js-map-entries
JS_DEFINE_NATIVE_FUNCTION(CSSFontFeatureValuesMapPrototype::entries)
{
    WebIDL::log_trace(vm, "CSSFontFeatureValuesMapPrototype::entries");
    auto& realm = *vm.current_realm();

    // 1. Let O be the this value, implementation-checked against A with identifier "entries" and type "method".
    auto* this_impl = TRY(impl_from(vm));

    // 2. Let map be the map entries of the IDL value that represents a reference to O.
    GC::Ref<JS::Map> map = this_impl->map_entries();

    // 3. Return the result of creating a map iterator from map with kind "key+value".
    return JS::MapIterator::create(realm, *map, PropertyKind::KeyAndValue);
}

// https://webidl.spec.whatwg.org/#js-map-keys
JS_DEFINE_NATIVE_FUNCTION(CSSFontFeatureValuesMapPrototype::keys)
{
    WebIDL::log_trace(vm, "CSSFontFeatureValuesMapPrototype::keys");
    auto& realm = *vm.current_realm();

    // 1. Let O be the this value, implementation-checked against A with identifier "keys" and type "method".
    auto* this_impl = TRY(impl_from(vm));

    // 2. Let map be the map entries of the IDL value that represents a reference to O.
    GC::Ref<JS::Map> map = this_impl->map_entries();

    // 3. Return the result of creating a map iterator from map with kind "key".
    return JS::MapIterator::create(realm, *map, PropertyKind::Key);
}

// https://webidl.spec.whatwg.org/#js-map-values
JS_DEFINE_NATIVE_FUNCTION(CSSFontFeatureValuesMapPrototype::values)
{
    WebIDL::log_trace(vm, "CSSFontFeatureValuesMapPrototype::values");
    auto& realm = *vm.current_realm();

    // 1. Let O be the this value, implementation-checked against A with identifier "values" and type "method".
    auto* this_impl = TRY(impl_from(vm));

    // 2. Let map be the map entries of the IDL value that represents a reference to O.
    GC::Ref<JS::Map> map = this_impl->map_entries();

    // 3. Return the result of creating a map iterator from map with kind "value".
    return JS::MapIterator::create(realm, *map, PropertyKind::Value);
}

// https://webidl.spec.whatwg.org/#js-map-forEach
JS_DEFINE_NATIVE_FUNCTION(CSSFontFeatureValuesMapPrototype::for_each)
{
    WebIDL::log_trace(vm, "CSSFontFeatureValuesMapPrototype::for_each");

    // 1. Let O be the this value, implementation-checked against A with identifier "forEach" and type "method".
    auto* this_impl = TRY(impl_from(vm));

    // 2. Let map be the map entries of the IDL value that represents a reference to O.
    GC::Ref<JS::Map> map = this_impl->map_entries();

    // 3. Let callbackFn be the first argument passed to the function, or undefined if not supplied.
    auto callback = vm.argument(0);

    // 4. If IsCallable(callbackFn) is false, throw a TypeError.
    if (!callback.is_function())
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAFunction, callback);

    // 5. Let thisArg be the second argument passed to the function, or undefined if not supplied.
    auto this_arg = vm.argument(1);

    // 6. For each key → value of map:
    for (auto [key, value] : *map) {
        // 1. Let jsKey and jsValue be key and value converted to a JavaScript value.
        // 2. Perform ? Call(callbackFn, thisArg, « jsValue, jsKey, O »).
        TRY(JS::call(vm, callback.as_function(), this_arg, value, key, this_impl));
    }

    // 7. Return undefined.
    return JS::js_undefined();
}

// https://webidl.spec.whatwg.org/#js-map-get
JS_DEFINE_NATIVE_FUNCTION(CSSFontFeatureValuesMapPrototype::get)
{
    WebIDL::log_trace(vm, "CSSFontFeatureValuesMapPrototype::get");

    // 1. Let O be the this value, implementation-checked against A with identifier "get" and type "method".
    auto* this_impl = TRY(impl_from(vm));

    // 2. Let map be the map entries of the IDL value that represents a reference to O.
    GC::Ref<JS::Map> map = this_impl->map_entries();

    // 3. Let keyType be the key type specified in the maplike declaration.
    // 4. Let keyArg be the first argument passed to this function, or undefined if not supplied.
    // 5. Let key be keyArg converted to an IDL value of type keyType.
    auto key = vm.argument(0);
        if (!key.is_string())
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "String");


    // FIXME: 6. If key is -0, set key to +0.

    // 7. If map[key] exists, then return map[key], converted to a JavaScript value.
    auto result = map->map_get(key);
    return result.value_or(JS::js_undefined());
}

// https://webidl.spec.whatwg.org/#js-map-has
JS_DEFINE_NATIVE_FUNCTION(CSSFontFeatureValuesMapPrototype::has)
{
    WebIDL::log_trace(vm, "CSSFontFeatureValuesMapPrototype::has");

    // 1. Let O be the this value, implementation-checked against A with identifier "has" and type "method".
    auto* this_impl = TRY(impl_from(vm));

    // 2. Let map be the map entries of the IDL value that represents a reference to O.
    GC::Ref<JS::Map> map = this_impl->map_entries();

    // 3. Let keyType be the key type specified in the maplike declaration.
    // 4. Let keyArg be the first argument passed to this function, or undefined if not supplied.
    // 5. Let key be keyArg converted to an IDL value of type keyType.
    auto key = vm.argument(0);
        if (!key.is_string())
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "String");


    // FIXME: 6. If key is -0, set key to +0.

    // 7. If map[key] exists, then return true; otherwise return false.
    return map->map_has(key);
}
// https://webidl.spec.whatwg.org/#js-map-delete
JS_DEFINE_NATIVE_FUNCTION(CSSFontFeatureValuesMapPrototype::delete_)
{
    WebIDL::log_trace(vm, "CSSFontFeatureValuesMapPrototype::delete_");

    // 1. Let O be the this value, implementation-checked against A with identifier "delete" and type "method".
    auto* this_impl = TRY(impl_from(vm));

    // 2. Let map be the map entries of the IDL value that represents a reference to O.
    GC::Ref<JS::Map> map = this_impl->map_entries();

    // 3. Let keyType be the key type specified in the maplike declaration.
    // 4. Let keyArg be the first argument passed to this function, or undefined if not supplied.
    // 5. Let key be keyArg converted to an IDL value of type keyType.
    auto key = vm.argument(0);
        if (!key.is_string())
        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "String");


    // FIXME: 6. If key is -0, set key to +0.

    // 7. Let retVal be true if map[key] exists, or else false.
    // 8. Remove map[key].
    auto result = map->map_remove(key);
    this_impl->on_map_modified_from_js({});

    // 9. Return retVal.
    return result;
}

// https://webidl.spec.whatwg.org/#js-map-delete
JS_DEFINE_NATIVE_FUNCTION(CSSFontFeatureValuesMapPrototype::clear)
{
    WebIDL::log_trace(vm, "CSSFontFeatureValuesMapPrototype::clear");

    // 1. Let O be the this value, implementation-checked against A with identifier "delete" and type "method".
    auto* this_impl = TRY(impl_from(vm));

    // 2. Let map be the map entries of the IDL value that represents a reference to O.
    GC::Ref<JS::Map> map = this_impl->map_entries();

    // 3. Clear map.
    // NOTE: The map is preserved because there may be existing iterators, currently suspended, iterating over it.
    map->map_clear();
    this_impl->on_map_modified_from_js({});

    // 4. Return undefined.
    return JS::js_undefined();
}

} // namespace Web::Bindings
