#include <jsi/jsi.h>

#include "main.h"
#include "Utils.h"
#include "DQL.h"
#include "Differ.h"
#include "Document.h"
#include "Authentication.h"
#include "Sync.h"
#include "Lifecycle.h"
#include "Misc.h"
#include "Identity.h"
#include "Presence.h"
#include "Attachment.h"
#include "LiveQuery.h"
#include "Collection.h"
#include "Logger.h"
#include "SmallPeerInfo.h"
#include "Transports.h"
#include "IO.h"
#include "FFIUtils.h"
#include "ConnectionRequest.h"
#include "GlobalCallInvoker.h"
#include "Transaction.h"

// Note: `runtime` variable must exist in scope.
#define REGISTER_JS_FUNCTION(fname) runtime.global().setProperty(runtime, STRINGIFY(fname), fname(runtime));

using namespace std;

namespace sharedjsi
{

  void install(Runtime &runtime, std::shared_ptr<facebook::react::CallInvoker> callInvoker,
               Object &parameters)
  {
    globalCallInvoker = callInvoker;

    // Identity
    REGISTER_JS_FUNCTION(ditto_identity_config_make_online_playground);
    REGISTER_JS_FUNCTION(ditto_identity_config_make_offline_playground);
    REGISTER_JS_FUNCTION(ditto_identity_config_make_manual_v0);
    REGISTER_JS_FUNCTION(ditto_identity_config_make_shared_key);
    REGISTER_JS_FUNCTION(ditto_identity_config_make_online_with_authentication);

    // Lifecycle
    REGISTER_JS_FUNCTION(ditto_init_sdk_version);
    REGISTER_JS_FUNCTION(dittoffi_ditto_try_new_blocking);
    REGISTER_JS_FUNCTION(ditto_shutdown);
    REGISTER_JS_FUNCTION(ditto_free);

    // Sync
    REGISTER_JS_FUNCTION(dittoffi_ditto_is_activated);
    REGISTER_JS_FUNCTION(dittoffi_ditto_is_sync_active);
    REGISTER_JS_FUNCTION(dittoffi_ditto_try_start_sync);
    REGISTER_JS_FUNCTION(dittoffi_ditto_stop_sync);

    // Transaction
    REGISTER_JS_FUNCTION(dittoffi_store_transactions);
    REGISTER_JS_FUNCTION(dittoffi_store_begin_transaction_async_throws);
    REGISTER_JS_FUNCTION(dittoffi_transaction_complete_async_throws);
    REGISTER_JS_FUNCTION(dittoffi_transaction_execute_async_throws);
    REGISTER_JS_FUNCTION(dittoffi_transaction_info);
    REGISTER_JS_FUNCTION(dittoffi_transaction_free);

    // FFI Utils
    REGISTER_JS_FUNCTION(refCStringToString);
    REGISTER_JS_FUNCTION(boxCStringIntoString);
    REGISTER_JS_FUNCTION(sliceRefToUInt8Array);
    REGISTER_JS_FUNCTION(sliceBoxedToUInt8Array);
    REGISTER_JS_FUNCTION(withOutBoxCBytes);

    // Authentication
    REGISTER_JS_FUNCTION(ditto_auth_client_get_app_id);
    REGISTER_JS_FUNCTION(ditto_auth_client_is_web_valid);
    REGISTER_JS_FUNCTION(ditto_auth_client_get_site_id);
    REGISTER_JS_FUNCTION(ditto_auth_client_make_login_provider);
    REGISTER_JS_FUNCTION(ditto_auth_set_login_provider);
    REGISTER_JS_FUNCTION(ditto_auth_client_login_with_token);
    REGISTER_JS_FUNCTION(ditto_auth_client_login_with_token_and_feedback);
    REGISTER_JS_FUNCTION(ditto_auth_client_login_with_credentials);
    REGISTER_JS_FUNCTION(ditto_auth_client_logout);
    REGISTER_JS_FUNCTION(ditto_auth_client_user_id);
    REGISTER_JS_FUNCTION(dittoffi_ditto_set_authentication_status_handler);
    REGISTER_JS_FUNCTION(dittoffi_authentication_status_is_authenticated);
    REGISTER_JS_FUNCTION(dittoffi_authentication_status_user_id);
    REGISTER_JS_FUNCTION(dittoffi_authentication_status_free);

    // DQL
    REGISTER_JS_FUNCTION(dittoffi_try_experimental_register_change_observer_str_detached);
    REGISTER_JS_FUNCTION(dittoffi_try_add_sync_subscription);
    REGISTER_JS_FUNCTION(dittoffi_try_remove_sync_subscription);
    REGISTER_JS_FUNCTION(dittoffi_try_exec_statement);
    REGISTER_JS_FUNCTION(dittoffi_query_result_item_count);
    REGISTER_JS_FUNCTION(dittoffi_query_result_item_at);
    REGISTER_JS_FUNCTION(dittoffi_query_result_mutated_document_id_count);
    REGISTER_JS_FUNCTION(dittoffi_query_result_mutated_document_id_at);
    REGISTER_JS_FUNCTION(dittoffi_query_result_item_cbor);
    REGISTER_JS_FUNCTION(dittoffi_query_result_item_json);
    REGISTER_JS_FUNCTION(dittoffi_query_result_free);
    REGISTER_JS_FUNCTION(dittoffi_query_result_item_free);
    REGISTER_JS_FUNCTION(dittoffi_query_result_item_new);

    // Differ
    REGISTER_JS_FUNCTION(dittoffi_differ_new);
    REGISTER_JS_FUNCTION(dittoffi_differ_diff);
    REGISTER_JS_FUNCTION(dittoffi_differ_free);

    // Documents
    REGISTER_JS_FUNCTION(ditto_document_id_query_compatible);
    REGISTER_JS_FUNCTION(ditto_document_id);
    REGISTER_JS_FUNCTION(ditto_document_free);
    REGISTER_JS_FUNCTION(ditto_document_get_cbor_with_path_type);
    REGISTER_JS_FUNCTION(ditto_validate_document_id);

    // Presence
    REGISTER_JS_FUNCTION(ditto_presence_v1);
    REGISTER_JS_FUNCTION(ditto_register_presence_v1_callback);
    REGISTER_JS_FUNCTION(ditto_clear_presence_callback);
    REGISTER_JS_FUNCTION(ditto_presence_v3);
    REGISTER_JS_FUNCTION(ditto_register_presence_v3_callback);
    REGISTER_JS_FUNCTION(ditto_clear_presence_v3_callback);
    REGISTER_JS_FUNCTION(dittoffi_presence_peer_metadata_json);
    REGISTER_JS_FUNCTION(dittoffi_presence_try_set_peer_metadata_json);
    REGISTER_JS_FUNCTION(dittoffi_presence_set_connection_request_handler);

    // Attachment
    REGISTER_JS_FUNCTION(ditto_new_attachment_from_file);
    REGISTER_JS_FUNCTION(ditto_new_attachment_from_bytes);
    REGISTER_JS_FUNCTION(ditto_resolve_attachment);
    REGISTER_JS_FUNCTION(ditto_cancel_resolve_attachment);
    REGISTER_JS_FUNCTION(ditto_free_attachment_handle);
    REGISTER_JS_FUNCTION(ditto_get_attachment_status);
    REGISTER_JS_FUNCTION(ditto_get_complete_attachment_data);
    REGISTER_JS_FUNCTION(ditto_get_complete_attachment_path);

    // LiveQuery
    REGISTER_JS_FUNCTION(ditto_live_query_start);
    REGISTER_JS_FUNCTION(ditto_live_query_stop);
    REGISTER_JS_FUNCTION(ditto_live_query_signal_available_next);

    // Collection
    REGISTER_JS_FUNCTION(ditto_collection_insert_value);

    // Logger
    REGISTER_JS_FUNCTION(ditto_log);
    REGISTER_JS_FUNCTION(ditto_logger_init);
    REGISTER_JS_FUNCTION(ditto_logger_set_log_file);
    REGISTER_JS_FUNCTION(ditto_logger_minimum_log_level_get);
    REGISTER_JS_FUNCTION(ditto_logger_minimum_log_level);
    REGISTER_JS_FUNCTION(ditto_logger_emoji_headings_enabled_get);
    REGISTER_JS_FUNCTION(ditto_logger_emoji_headings_enabled);
    REGISTER_JS_FUNCTION(ditto_logger_enabled_get);
    REGISTER_JS_FUNCTION(ditto_logger_enabled);
    REGISTER_JS_FUNCTION(dittoffi_logger_try_export_to_file_async);
    REGISTER_JS_FUNCTION(ditto_logger_set_custom_log_cb);

    // Small Peer Info
    REGISTER_JS_FUNCTION(ditto_small_peer_info_get_is_enabled);
    REGISTER_JS_FUNCTION(ditto_small_peer_info_set_enabled);
    REGISTER_JS_FUNCTION(ditto_small_peer_info_get_sync_scope);
    REGISTER_JS_FUNCTION(ditto_small_peer_info_set_sync_scope);
    REGISTER_JS_FUNCTION(ditto_small_peer_info_get_metadata);
    REGISTER_JS_FUNCTION(ditto_small_peer_info_set_metadata);

    // Transports
    REGISTER_JS_FUNCTION(ditto_sdk_transports_init);
    REGISTER_JS_FUNCTION(ditto_sdk_transports_error_new);
    REGISTER_JS_FUNCTION(ditto_sdk_transports_error_value);
    REGISTER_JS_FUNCTION(ditto_sdk_transports_error_free);
    REGISTER_JS_FUNCTION(dittoffi_ditto_try_set_transport_config);
    REGISTER_JS_FUNCTION(dittoffi_ditto_transport_config);
    REGISTER_JS_FUNCTION(dittoffi_ditto_set_cloud_sync_enabled);
    REGISTER_JS_FUNCTION(ditto_register_transport_condition_changed_callback);

    // Connection Request
    REGISTER_JS_FUNCTION(dittoffi_connection_request_peer_key_string);
    REGISTER_JS_FUNCTION(dittoffi_connection_request_peer_metadata_json);
    REGISTER_JS_FUNCTION(dittoffi_connection_request_identity_service_json);
    REGISTER_JS_FUNCTION(dittoffi_connection_request_authorize);
    REGISTER_JS_FUNCTION(dittoffi_connection_request_free);
    REGISTER_JS_FUNCTION(dittoffi_connection_request_connection_type);

    // Misc
    REGISTER_JS_FUNCTION(ditto_tick);
    REGISTER_JS_FUNCTION(ditto_set_device_name);
    REGISTER_JS_FUNCTION(ditto_get_sdk_version);
    REGISTER_JS_FUNCTION(dittoffi_get_sdk_semver);
    REGISTER_JS_FUNCTION(ditto_error_message);
    REGISTER_JS_FUNCTION(dittoffi_error_code);
    REGISTER_JS_FUNCTION(dittoffi_error_description);
    REGISTER_JS_FUNCTION(dittoffi_error_free);
    REGISTER_JS_FUNCTION(dittoffi_try_verify_license);
    REGISTER_JS_FUNCTION(ditto_run_garbage_collection);
    REGISTER_JS_FUNCTION(ditto_disable_sync_with_v3);
    REGISTER_JS_FUNCTION(dittoffi_base64_encode);
    REGISTER_JS_FUNCTION(dittoffi_try_base64_decode);
    REGISTER_JS_FUNCTION(dittoffi_crypto_generate_secure_random_token);

    // IO
    REGISTER_JS_FUNCTION(readFile);
    REGISTER_JS_FUNCTION(copyFile);

    // createDirectory() doesn't fit our macro, as it requires an additional parameter
    String defaultDirectory = parameters.getProperty(runtime, "defaultDirectory").asString(runtime);
    runtime.global().setProperty(runtime, "createDirectory", createDirectory(runtime, defaultDirectory));
  }
}
