#include "Utils.h"
#include "Arc.hpp"

#include <Lifecycle.h>

namespace sharedjsi
{

DEFINE_RETAIN_RELEASE_FOR(Function, v_retain, v_release);

Function ditto_shutdown(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    ::ditto_shutdown(jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]));
    return {};
  });
}

Function ditto_free(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    ::ditto_free(jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]));
    return {};
  });
}

Function ditto_init_sdk_version(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    std::string platform_str = arguments[0].toString(runtime).utf8(runtime);
    std::string language_str = arguments[1].toString(runtime).utf8(runtime);
    std::string semver_str = arguments[2].toString(runtime).utf8(runtime);
    const char *semver = semver_str.c_str();

    Language_t language = LANGUAGE_UNKNOWN;
    if (language_str == "JavaScript") {
      language = LANGUAGE_JAVA_SCRIPT;
    }

    Platform_t platform = PLATFORM_UNKNOWN;
    if (platform_str == "iOS") {
      platform = PLATFORM_IOS;
    } else if (platform_str == "Android") {
      platform = PLATFORM_ANDROID;
    } else if (platform_str == "macOS") {
      platform = PLATFORM_MAC;
    }

    ::ditto_init_sdk_version(platform, language, semver);
    return {};
  });
}

Function dittoffi_ditto_open_throws(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          3,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    std::vector<uint8_t> config_cbor_vec = jsTypedArrayToCVector(runtime, arguments[0]);
    slice_ref_uint8_t config_slice = borrowVecAsRefSlice(config_cbor_vec);

    std::string transport_config_mode_str = arguments[1].toString(runtime).utf8(runtime);
    
    TransportConfigMode_t transport_config_mode;
    if (transport_config_mode_str == "PlatformDependent") {
        transport_config_mode = TRANSPORT_CONFIG_MODE_PLATFORM_DEPENDENT;
    } else if (transport_config_mode_str == "PlatformIndependent") {
        transport_config_mode = TRANSPORT_CONFIG_MODE_PLATFORM_INDEPENDENT;
    } else {
        throw std::invalid_argument("Invalid transport configuration mode: " + transport_config_mode_str);
    }

    std::string default_root_dir_str = jsTypedArrayToCString(runtime, arguments[2]);
    const char* default_root_directory = default_root_dir_str.c_str();

    dittoffi_result_CDitto_ptr_t res = ::dittoffi_ditto_open_throws(
        config_slice,
        transport_config_mode,
        default_root_directory
    );

    Object obj(runtime);
    obj.setProperty(runtime, "success", cPointerToJSPointerObject(runtime, res.success));
    obj.setProperty(runtime, "error", cPointerToJSPointerObject(runtime, res.error));
    return obj;
  });
}

Function dittoffi_DEFAULT_DATABASE_ID(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    const char* default_id = ::dittoffi_DEFAULT_DATABASE_ID();
    return cPointerToJSPointerObject<char>(runtime, default_id);
  });
}

Function dittoffi_DITTO_DEVELOPMENT_PROVIDER(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    const char* dev_provider = ::dittoffi_DITTO_DEVELOPMENT_PROVIDER();
    return cPointerToJSPointerObject<char>(runtime, dev_provider);
  });
}

Function dittoffi_ditto_config_default(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    slice_boxed_uint8_t config_cbor = ::dittoffi_ditto_config_default();
    return cSlicedBoxToJSSlicedBox(runtime, config_cbor);
  });
}

Function dittoffi_ditto_config(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          1,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    auto *ditto = jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]);
    slice_boxed_uint8_t config_cbor = ::dittoffi_ditto_config(ditto);
    return cSlicedBoxToJSSlicedBox(runtime, config_cbor);
  });
}


extern "C"
{
void v_ditto_open_result(void *ctx, dittoffi_result_CDitto_ptr_t res)
{
  Arc<Function> const& borrowedJsCallback = Arc<Function>::borrow_from_raw(ctx);
  // copy ctor / retain so as to keep the ArcPointee<Function> alive until the enqueued_call completes.
  Arc<Function> jsCallback(borrowedJsCallback);
  jsi_enqueue_call([=](Runtime &runtime) {
    Object obj(runtime);
    obj.setProperty(runtime, "success", cPointerToJSPointerObject(runtime, res.success));
    obj.setProperty(runtime, "error", cPointerToJSPointerObject(runtime, res.error));
    jsCallback->call(runtime, obj);
  });
}
}

Function dittoffi_ditto_open_async_throws(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          4,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    // Extract CBOR config from TypedArray using JSI helper
    std::vector<uint8_t> config_cbor_vec = jsTypedArrayToCVector(runtime, arguments[0]);
    slice_ref_uint8_t config_slice = borrowVecAsRefSlice(config_cbor_vec);

    // Extract transport config mode
    std::string transport_config_mode_str = arguments[1].toString(runtime).utf8(runtime);
    
    TransportConfigMode_t transport_config_mode;
    if (transport_config_mode_str == "PlatformDependent") {
        transport_config_mode = TRANSPORT_CONFIG_MODE_PLATFORM_DEPENDENT;
    } else if (transport_config_mode_str == "PlatformIndependent") {
        transport_config_mode = TRANSPORT_CONFIG_MODE_PLATFORM_INDEPENDENT;
    } else {
        throw std::invalid_argument("Invalid transport configuration mode: " + transport_config_mode_str);
    }

    std::string default_root_dir_str = jsTypedArrayToCString(runtime, arguments[2]);
    const char* default_root_directory = default_root_dir_str.c_str();

    // Extract callback
    Function callback = arguments[3].getObject(runtime).getFunction(runtime);
    Arc<Function> wrappedStateFfiWrapper(std::move(callback));

    // Call the async FFI function
    ::dittoffi_ditto_open_async_throws(
        config_slice,
        transport_config_mode,
        default_root_directory,
        {
          .env_ptr = Arc<Function>::into_raw(std::move(wrappedStateFfiWrapper)),
          .call = v_ditto_open_result,
          .free = v_release,
        }
    );

    return Value();
  });
}


}
