/*
 * Copyright (c) 2020-2023, Linus Groh <linusg@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/FunctionConstructor.h>
#include <LibJS/Runtime/FunctionObject.h>
#include <LibJS/Runtime/GeneratorPrototype.h>
#include <LibJS/Runtime/GlobalEnvironment.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Realm.h>
#include <LibJS/RustIntegration.h>
#include <LibJS/SourceCode.h>

namespace JS {

GC_DEFINE_ALLOCATOR(FunctionConstructor);

FunctionConstructor::FunctionConstructor(Realm& realm)
    : NativeFunction(realm.vm().names.Function.as_string(), realm.intrinsics().function_prototype())
{
}

void FunctionConstructor::initialize(Realm& realm)
{
    auto& vm = this->vm();
    Base::initialize(realm);

    // 20.2.2.2 Function.prototype, https://tc39.es/ecma262/#sec-function.prototype
    define_direct_property(vm.names.prototype, realm.intrinsics().function_prototype(), 0);

    define_direct_property(vm.names.length, Value(1), Attribute::Configurable);
}

// 20.2.1.1.1 CreateDynamicFunction ( constructor, newTarget, kind, parameterArgs, bodyArg ), https://tc39.es/ecma262/#sec-createdynamicfunction
ThrowCompletionOr<GC::Ref<ECMAScriptFunctionObject>> FunctionConstructor::create_dynamic_function(VM& vm, FunctionObject& constructor, FunctionObject* new_target, FunctionKind kind, ReadonlySpan<Value> parameter_args, Value body_arg)
{
    // 1. If newTarget is undefined, set newTarget to constructor.
    if (new_target == nullptr)
        new_target = &constructor;

    StringView prefix;
    GC::Ref<Object> (Intrinsics::*fallback_prototype)() = nullptr;

    switch (kind) {
    // 2. If kind is normal, then
    case FunctionKind::Normal:
        // a. Let prefix be "function".
        prefix = "function"sv;

        // b. Let exprSym be the grammar symbol FunctionExpression.
        // c. Let bodySym be the grammar symbol FunctionBody[~Yield, ~Await].
        // d. Let parameterSym be the grammar symbol FormalParameters[~Yield, ~Await].

        // e. Let fallbackProto be "%Function.prototype%".
        fallback_prototype = &Intrinsics::function_prototype;
        break;

    // 3. Else if kind is generator, then
    case FunctionKind::Generator:
        // a. Let prefix be "function*".
        prefix = "function*"sv;

        // b. Let exprSym be the grammar symbol GeneratorExpression.
        // c. Let bodySym be the grammar symbol GeneratorBody.
        // d. Let parameterSym be the grammar symbol FormalParameters[+Yield, ~Await].

        // e. Let fallbackProto be "%GeneratorFunction.prototype%".
        fallback_prototype = &Intrinsics::generator_function_prototype;
        break;

    // 4. Else if kind is async, then
    case FunctionKind::Async:
        // a. Let prefix be "async function".
        prefix = "async function"sv;

        // b. Let exprSym be the grammar symbol AsyncFunctionExpression.
        // c. Let bodySym be the grammar symbol AsyncFunctionBody.
        // d. Let parameterSym be the grammar symbol FormalParameters[~Yield, +Await].

        // e. Let fallbackProto be "%AsyncFunction.prototype%".
        fallback_prototype = &Intrinsics::async_function_prototype;
        break;

    // 5. Else,
    case FunctionKind::AsyncGenerator:
        // a. Assert: kind is async-generator.

        // b. Let prefix be "async function*".
        prefix = "async function*"sv;

        // c. Let exprSym be the grammar symbol AsyncGeneratorExpression.
        // d. Let bodySym be the grammar symbol AsyncGeneratorBody.
        // e. Let parameterSym be the grammar symbol FormalParameters[+Yield, +Await].

        // f. Let fallbackProto be "%AsyncGeneratorFunction.prototype%".
        fallback_prototype = &Intrinsics::async_generator_function_prototype;
        break;

    default:
        VERIFY_NOT_REACHED();
    }

    // 6. Let argCount be the number of elements in parameterArgs.
    auto arg_count = parameter_args.size();

    // 7. Let parameterStrings be a new empty List.
    Vector<String> parameter_strings;
    parameter_strings.ensure_capacity(arg_count);

    // 8. For each element arg of parameterArgs, do
    for (auto const& parameter_value : parameter_args) {
        // a. Append ? ToString(arg) to parameterStrings.
        parameter_strings.unchecked_append(TRY(parameter_value.to_string(vm)));
    }

    // 9. Let bodyString be ? ToString(bodyArg).
    auto body_string = TRY(body_arg.to_string(vm));

    // 10. Let currentRealm be the current Realm Record.
    auto& realm = *vm.current_realm();

    // 11. Let P be the empty String.
    String parameters_string;

    // 12. If argCount > 0, then
    if (arg_count > 0) {
        // a. Set P to parameterStrings[0].
        // b. Let k be 1.
        // c. Repeat, while k < argCount,
        //     i. Let nextArgString be parameterStrings[k].
        //     ii. Set P to the string-concatenation of P, "," (a comma), and nextArgString.
        //     iii. Set k to k + 1.
        parameters_string = MUST(String::join(',', parameter_strings));
    }

    // 13. Let bodyParseString be the string-concatenation of 0x000A (LINE FEED), bodyString, and 0x000A (LINE FEED).
    auto body_parse_string = ByteString::formatted("\n{}\n", body_string);

    // 14. Let sourceString be the string-concatenation of prefix, " anonymous(", P, 0x000A (LINE FEED), ") {", bodyParseString, and "}".
    // 15. Let sourceText be StringToCodePoints(sourceString).
    auto source_text = ByteString::formatted("{} anonymous({}\n) {{{}}}", prefix, parameters_string, body_parse_string);

    // 16. Perform ? HostEnsureCanCompileStrings(currentRealm, parameterStrings, bodyString, sourceString, FUNCTION, parameterArgs, bodyArg).
    TRY(vm.host_ensure_can_compile_strings(realm, parameter_strings, body_string, source_text, CompilationType::Function, parameter_args, body_arg));

    GC::Ptr<SharedFunctionInstanceData> function_data;

    auto rust_compilation = RustIntegration::compile_dynamic_function(vm, source_text, parameters_string, body_parse_string, kind);
    if (!rust_compilation.has_value())
        return vm.throw_completion<SyntaxError>("Failed to compile dynamic function"_string);
    if (rust_compilation->is_error())
        return vm.throw_completion<SyntaxError>(rust_compilation->release_error());
    function_data = rust_compilation->value();

    // 25. Let proto be ? GetPrototypeFromConstructor(newTarget, fallbackProto).
    auto* prototype = TRY(get_prototype_from_constructor(vm, *new_target, fallback_prototype));

    // 26. Let env be currentRealm.[[GlobalEnv]].
    auto& environment = realm.global_environment();

    // 27. Let privateEnv be null.
    PrivateEnvironment* private_environment = nullptr;

    auto function = ECMAScriptFunctionObject::create_from_function_data(
        realm,
        *function_data,
        &environment,
        private_environment,
        *prototype);

    // FIXME: Remove the name argument from create() and do this instead.
    // 29. Perform SetFunctionName(F, "anonymous").

    // 30. If kind is generator, then
    if (kind == FunctionKind::Generator) {
        // a. Let prototype be OrdinaryObjectCreate(%GeneratorFunction.prototype.prototype%).
        prototype = Object::create_prototype(realm, realm.intrinsics().generator_function_prototype_prototype());

        // b. Perform ! DefinePropertyOrThrow(F, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
        function->define_direct_property(vm.names.prototype, prototype, Attribute::Writable);
    }
    // 31. Else if kind is asyncGenerator, then
    else if (kind == FunctionKind::AsyncGenerator) {
        // a. Let prototype be OrdinaryObjectCreate(%AsyncGeneratorFunction.prototype.prototype%).
        prototype = Object::create_prototype(realm, realm.intrinsics().async_generator_function_prototype_prototype());

        // b. Perform ! DefinePropertyOrThrow(F, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
        function->define_direct_property(vm.names.prototype, prototype, Attribute::Writable);
    }
    // 32. Else if kind is normal, perform MakeConstructor(F).
    else if (kind == FunctionKind::Normal) {
        // FIXME: Implement MakeConstructor
        prototype = Object::create_prototype(realm, realm.intrinsics().object_prototype());
        prototype->define_direct_property(vm.names.constructor, function, Attribute::Writable | Attribute::Configurable);
        function->define_direct_property(vm.names.prototype, prototype, Attribute::Writable);
    }

    // 33. NOTE: Functions whose kind is async are not constructible and do not have a [[Construct]] internal method or a "prototype" property.

    // 34. Return F.
    return function;
}

// 20.2.1.1 Function ( p1, p2, … , pn, body ), https://tc39.es/ecma262/#sec-function-p1-p2-pn-body
ThrowCompletionOr<Value> FunctionConstructor::call()
{
    return TRY(construct(*this));
}

// 20.2.1.1 Function ( ...parameterArgs, bodyArg ), https://tc39.es/ecma262/#sec-function-p1-p2-pn-body
ThrowCompletionOr<GC::Ref<Object>> FunctionConstructor::construct(FunctionObject& new_target)
{
    auto& vm = this->vm();

    ReadonlySpan<Value> arguments = vm.running_execution_context().arguments_span();

    ReadonlySpan<Value> parameter_args = arguments;
    if (!parameter_args.is_empty())
        parameter_args = parameter_args.slice(0, parameter_args.size() - 1);

    // 1. Let C be the active function object.
    auto* constructor = vm.active_function_object();

    // 2. If bodyArg is not present, set bodyArg to the empty String.
    Value body_arg = &vm.empty_string();
    if (!arguments.is_empty())
        body_arg = arguments.last();

    // 3. Return ? CreateDynamicFunction(C, NewTarget, normal, parameterArgs, bodyArg).
    return TRY(create_dynamic_function(vm, *constructor, &new_target, FunctionKind::Normal, parameter_args, body_arg));
}

}
