#include "Utils.h"

#include <Attachment.h>
#include <Arc.hpp>

namespace sharedjsi
{

class AttachmentResolver {
public:
  Function onCompleteCb;
  Function onProgressCb;
  Function onDeletedCb;

  AttachmentResolver(Function onComplete, Function onProgress, Function onDeleted)
  : onCompleteCb(std::move(onComplete)), onProgressCb(std::move(onProgress)), onDeletedCb(std::move(onDeleted)) {}
};

extern "C" {
void v_on_complete(void *ctx, AttachmentHandle_t *handle)
{
  jsi_enqueue_call([=](Runtime &runtime) {
    auto jsObject = reinterpret_cast<AttachmentResolver *>(ctx);
    jsObject->onCompleteCb.call(runtime, cPointerToJSPointerObject<AttachmentHandle>(runtime, handle));
  });
}

void v_on_progress(void *ctx, uint64_t downloaded, uint64_t to_download)
{
  jsi_enqueue_call([=](Runtime &runtime) {
    auto jsObject = reinterpret_cast<AttachmentResolver *>(ctx);
    jsObject->onProgressCb.call(runtime, uint64ToBigIntOrNumber(runtime, downloaded), uint64ToBigIntOrNumber(runtime, to_download));
  });
}

void v_on_deleted(void *ctx)
{
  jsi_enqueue_call([=](Runtime &runtime) {
    auto jsObject = reinterpret_cast<AttachmentResolver *>(ctx);
    jsObject->onDeletedCb.call(runtime);
  });
}
}

Function ditto_cancel_resolve_attachment(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
                                          {
    auto *ditto = jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]);
    std::vector<uint8_t> id_vec = jsTypedArrayToCVector(runtime, arguments[1]);
    slice_ref_uint8_t id = borrowVecAsOptRefSlice(id_vec);
    uint64_t cancelToken = bigIntOrNumberToUint64(runtime, arguments[2]);

    return static_cast<double>(::ditto_cancel_resolve_attachment(ditto, id, cancelToken));
  });
}

Function ditto_free_attachment_handle(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_attachment_handle(jsPointerObjectToCPointer<AttachmentHandle_t>(runtime, arguments[0]));
    return {};
  });
}

Function ditto_get_complete_attachment_data(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
                                          {
    auto *ditto = jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]);
    auto* handle = jsPointerObjectToCPointer<AttachmentHandle_t>(runtime, arguments[1]);
    AttachmentDataResult_t res = ::ditto_get_complete_attachment_data(ditto, handle);

    Object obj(runtime);
    obj.setProperty(runtime, "status_code", static_cast<double>(res.status));
    obj.setProperty(runtime, "data", cSlicedBoxToJSSlicedBox(runtime, res.data));
    return obj;
  });
}

Function ditto_get_complete_attachment_path(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
                                          {
    auto *ditto = jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]);
    auto *attachmentHandler = jsPointerObjectToCPointer<AttachmentHandle_t>(runtime, arguments[1]);

    char *res = ::ditto_get_complete_attachment_path(ditto, attachmentHandler);
    return cPointerToJSPointerObject<char>(runtime, res);
  });
}

Function ditto_new_attachment_from_bytes(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
                                          {
    auto *ditto = jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]);
    std::vector<uint8_t> bytes_vec = jsTypedArrayToCVector(runtime, arguments[1]);
    slice_ref_uint8_t bytes = borrowVecAsRefSlice(bytes_vec);

    CAttachment_t c_attachment;
    uint32_t res = ::ditto_new_attachment_from_bytes(ditto, bytes, &c_attachment);

    if (res != 0) {
      // It failed, bubble up the error.
      return static_cast<double>(res);
    }

    Object  attachment = arguments[2].getObject(runtime);

    attachment.setProperty(runtime, "len", uint64ToBigIntOrNumber(runtime , c_attachment.len));
    // Factor this out as: cSliceToUInt8Array()
    std::vector<uint8_t> id(c_attachment.id.ptr, c_attachment.id.ptr + c_attachment.id.len);
    TypedArray<TypedArrayKind::Uint8Array> typedArray(runtime, id);
    attachment.setProperty(runtime, "id", typedArray);
    attachment.setProperty(runtime, "handle", cPointerToJSPointerObject(runtime, c_attachment.handle));

    return 0;
  });
}

Function ditto_new_attachment_from_file(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
                                          {
                                              auto *ditto = jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]);
    std::string source_path_str = jsTypedArrayToCString(runtime, arguments[1]);
    std::string file_operation_str = arguments[2].toString(runtime).utf8(runtime);

    AttachmentFileOperation_t file_operation;
    if (file_operation_str == "Copy") {
      file_operation = ATTACHMENT_FILE_OPERATION_COPY;
    } else if (file_operation_str == "Move") {
      file_operation = ATTACHMENT_FILE_OPERATION_MOVE;
    } else {
      throw "Invalid attachment file operation.";
    }

    CAttachment_t c_attachment;
    uint32_t res = ::ditto_new_attachment_from_file(ditto, source_path_str.c_str(), file_operation, &c_attachment);

    if (res != 0) {
      // It failed, bubble up the error.
      return static_cast<double>(res);
    }

    Object attachment = arguments[3].getObject(runtime);

    attachment.setProperty(runtime, "len", uint64ToBigIntOrNumber(runtime, c_attachment.len));
    // Factor this out as: cSliceToUInt8Array()
    std::vector<uint8_t> id(c_attachment.id.ptr, c_attachment.id.ptr + c_attachment.id.len);
    TypedArray<TypedArrayKind::Uint8Array> typedArray(runtime, id);
    attachment.setProperty(runtime, "id", typedArray);
    attachment.setProperty(runtime, "handle", cPointerToJSPointerObject(runtime, c_attachment.handle));

    return 0;
  });
}

Function ditto_resolve_attachment(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
                                          {
    auto *ditto = jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]);

    std::vector<uint8_t> id_vec = jsTypedArrayToCVector(runtime, arguments[1]);
    slice_ref_uint8_t id = borrowVecAsRefSlice(id_vec);

    Function onCompleteCallback = arguments[2].getObject(runtime).getFunction(runtime);
    Function onProgressCallback = arguments[3].getObject(runtime).getFunction(runtime);
    Function onDeleteCallback = arguments[4].getObject(runtime).getFunction(runtime);

    AttachmentResolver resolver(std::move(onCompleteCallback),
                                std::move(onProgressCallback),
                                std::move(onDeleteCallback));
    Arc<AttachmentResolver> wrappedStateFfiWrapper(std::move(resolver));

    CancelTokenResult_t res = ::ditto_resolve_attachment(ditto,
                                                         id,
                                                         Arc<AttachmentResolver>::into_raw(std::move(wrappedStateFfiWrapper)),
                                                         nullptr,
                                                         nullptr,
                                                         v_on_complete,
                                                         v_on_progress,
                                                         v_on_deleted);

    Object obj(runtime);
    obj.setProperty(runtime, "status_code", static_cast<double>(res.status_code));
    obj.setProperty(runtime, "cancel_token", int64ToBigIntOrNumber(runtime, res.cancel_token));
    return obj;
  });
}
}
