#include <jsi/jsi.h>

#include "main.h"

#include "Attachment.h"
#include "Authentication.h"
#include "ConnectionRequest.h"
#include "Differ.h"
#include "DQL.h"
#include "FFIUtils.h"
#include "GlobalCallInvoker.h"
#include "IO.h"
#include "Lifecycle.h"
#include "LiveQuery.h"
#include "Logger.h"
#include "Misc.h"
#include "Presence.h"
#include "SmallPeerInfo.h"
#include "StoreObserver.h"
#include "Sync.h"
#include "SyncSubscription.h"
#include "Transaction.h"
#include "Transports.h"
#include "Utils.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;

    // Lifecycle
    REGISTER_JS_FUNCTION(ditto_init_sdk_version);
    REGISTER_JS_FUNCTION(dittoffi_ditto_open_throws);
    REGISTER_JS_FUNCTION(dittoffi_DEFAULT_DATABASE_ID);
    REGISTER_JS_FUNCTION(dittoffi_DITTO_DEVELOPMENT_PROVIDER);
    REGISTER_JS_FUNCTION(dittoffi_ditto_config_default);
    REGISTER_JS_FUNCTION(dittoffi_ditto_config);
    REGISTER_JS_FUNCTION(dittoffi_ditto_transport_config);
    REGISTER_JS_FUNCTION(dittoffi_ditto_open_async_throws);
    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_is_web_valid);
    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_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_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);
    REGISTER_JS_FUNCTION(dittoffi_query_result_has_commit_id);
    REGISTER_JS_FUNCTION(dittoffi_query_result_commit_id);

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

    // Store Observer
    REGISTER_JS_FUNCTION(dittoffi_store_register_observer_throws);
    REGISTER_JS_FUNCTION(dittoffi_store_observers);
    REGISTER_JS_FUNCTION(dittoffi_store_observer_id);
    REGISTER_JS_FUNCTION(dittoffi_store_observer_query_string);
    REGISTER_JS_FUNCTION(dittoffi_store_observer_query_arguments_cbor);
    REGISTER_JS_FUNCTION(dittoffi_store_observer_query_arguments_json);
    REGISTER_JS_FUNCTION(dittoffi_store_observer_cancel);
    REGISTER_JS_FUNCTION(dittoffi_store_observer_is_cancelled);
    REGISTER_JS_FUNCTION(dittoffi_store_observer_free);

    // SyncSubscription
    REGISTER_JS_FUNCTION(dittoffi_sync_register_subscription_throws);
    REGISTER_JS_FUNCTION(dittoffi_sync_subscriptions);
    REGISTER_JS_FUNCTION(dittoffi_sync_subscription_id);
    REGISTER_JS_FUNCTION(dittoffi_sync_subscription_query_string);
    REGISTER_JS_FUNCTION(dittoffi_sync_subscription_query_arguments_cbor);
    REGISTER_JS_FUNCTION(dittoffi_sync_subscription_query_arguments_json);
    REGISTER_JS_FUNCTION(dittoffi_sync_subscription_cancel);
    REGISTER_JS_FUNCTION(dittoffi_sync_subscription_is_cancelled);
    REGISTER_JS_FUNCTION(dittoffi_sync_subscription_free);

    // Presence
    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_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);

    // Logger
    REGISTER_JS_FUNCTION(ditto_log);
    REGISTER_JS_FUNCTION(ditto_logger_init);
    REGISTER_JS_FUNCTION(ditto_logger_minimum_log_level_get);
    REGISTER_JS_FUNCTION(ditto_logger_minimum_log_level);
    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_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(ditto_register_transport_condition_changed_callback);
    REGISTER_JS_FUNCTION(dittoffi_transport_config_new);

    // 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(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(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);
    REGISTER_JS_FUNCTION(dittoffi_ditto_absolute_persistence_directory);

    // These functions don't fit our macro, as they require an additional parameter
    String defaultDirectory = parameters.getProperty(runtime, "defaultDirectory").asString(runtime);
    runtime.global().setProperty(runtime, "defaultDirectory", defaultDirectory);
    runtime.global().setProperty(runtime, "createDirectory", createDirectory(runtime, defaultDirectory));
  }
}
