//! C headers written in Rust.

use ::safer_ffi::{closure::boxed::*, prelude::derive_ReprC};

use super::*;
use crate::ffi_utils::repr_c;

// Ditto initialization API
#[apply(extern_type_polyfill!)]
unsafe extern "C" {
    pub fn dittoffi_ditto_open_async_throws(
        config_cbor: c_slice::Ref<'_, u8>,
        transport_config_mode: TransportConfigMode,
        default_root_directory: char_p::Ref<'_>,
        continuation: BoxDynFnMut1<(), FfiResult<repr_c::Box<Ditto>>>,
    );

    pub fn dittoffi_ditto_open_throws(
        config_cbor: c_slice::Ref<'_, u8>,
        transport_config_mode: TransportConfigMode,
        default_root_directory: char_p::Ref<'_>,
    ) -> FfiResult<repr_c::Box<Ditto>>;

    pub fn dittoffi_ditto_config(ditto: &Ditto) -> c_slice::Box<u8>;

    pub fn dittoffi_ditto_config_default() -> c_slice::Box<u8>;

    pub fn dittoffi_ditto_absolute_persistence_directory(ditto: &Ditto) -> char_p::Box;
}

#[repr(C)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TransportConfigMode {
    PlatformDependent,
    PlatformIndependent,
}

#[apply(extern_type_polyfill!)]
// no `#[link(…)]` since we let the build.rs script make a more informed choice about, for instance,
// dylib vs. static.
unsafe extern "C" {
    pub type Ditto: Send + Sync;
    #[drop(to impl FfiDropBox for Ditto)]
    pub fn ditto_free(ditto: c::Box<Ditto>);

    pub fn ditto_shutdown(ditto: &'_ Ditto);

    pub fn ditto_init_sdk_version(
        platform: Platform,
        language: Language,
        sdk_semver: char_p::Ref<'_>,
    );

    pub type IdentityConfig: Send + Sync;

    pub fn ditto_make(
        working_dir: char_p::Ref<'_>,
        identity_config: c::Box<IdentityConfig>,
    ) -> c::Box<Ditto>;

    pub fn ditto_logger_init();
    pub fn ditto_logger_enabled(enabled: bool);
    pub fn ditto_logger_enabled_get() -> bool;
    pub fn ditto_logger_emoji_headings_enabled(enabled: bool);
    pub fn ditto_logger_emoji_headings_enabled_get() -> bool;
    pub fn ditto_logger_minimum_log_level_get() -> CLogLevel;
    pub fn ditto_logger_minimum_log_level(log_level: CLogLevel);
    pub fn ditto_log(log_level: CLogLevel, msg: char_p::Ref<'_>);
    pub fn ditto_logger_set_custom_log_cb(custom_log_cb: Option<CustomLogCb>);

    pub unsafe fn ditto_sdk_transports_init(out_error: Out<'_, DittoSdkTransportsError>) -> bool;

    pub fn dittoffi_get_sdk_semver() -> char_p::Box;

    #[allow(non_snake_case)]
    pub fn dittoffi_DITTO_DEVELOPMENT_PROVIDER() -> char_p::Ref<'static>;

    pub fn ditto_verify_license(
        ditto: &'_ Ditto,
        license: char_p::Ref<'_>,
        out_err_msg: Option<Out<'_, Option<char_p::Box>>>,
    ) -> LicenseVerificationResult;

    pub fn ditto_run_garbage_collection(ditto: &'_ Ditto);

    pub fn ditto_disable_sync_with_v3(ditto: &'_ Ditto) -> i32;

    pub fn ditto_queries_hash<'s, 'l: 's>(
        ditto: &Ditto,
        coll_names: c_slice::Ref<'s, char_p::Ref<'l>>,
        queries: c_slice::Ref<'s, char_p::Ref<'l>>,
    ) -> c::Result<u64>;

    pub fn ditto_queries_hash_mnemonic<'s, 'l: 's>(
        ditto: &'_ Ditto,
        coll_names: c_slice::Ref<'s, char_p::Ref<'l>>,
        queries: c_slice::Ref<'s, char_p::Ref<'l>>,
    ) -> c::Result<char_p::Box>;

    pub fn ditto_document_id(document: &'_ Document) -> c_slice::Box<u8>;

    pub fn ditto_document_id_query_compatible(
        id: c_slice::Ref<'_, u8>,
        string_primitive_format: StringPrimitiveFormat,
    ) -> char_p::Box;

    pub fn ditto_validate_document_id(
        cbor: c_slice::Ref<'_, u8>,
        out_cbor: Out<'_, Option<c_slice::Box<u8>>>,
    ) -> c_uint;

    pub fn ditto_new_attachment_from_file(
        ditto: &'_ Ditto,
        source_path: char_p::Ref<'_>,
        file_operation: AttachmentFileOperation,
        out_attachment: Out<'_, Attachment>,
    ) -> c_uint;

    pub fn ditto_new_attachment_from_bytes(
        ditto: &'_ Ditto,
        bytes: c_slice::Ref<'_, u8>,
        out_attachment: Out<'_, Attachment>,
    ) -> c_uint;

    pub fn ditto_get_complete_attachment_path(
        ditto: &'_ Ditto,
        handle: &'_ AttachmentHandle,
    ) -> char_p::Box;

    pub unsafe fn ditto_resolve_attachment(
        ditto: &'_ Ditto,
        id: c_slice::Ref<'_, u8>,
        ctx: *mut c_void,
        retain: Option<unsafe extern "C" fn(*mut c_void)>,
        release: Option<unsafe extern "C" fn(*mut c_void)>,
        on_complete_cb: unsafe extern "C" fn(ctx: *mut c_void, c::Box<AttachmentHandle>),
        on_progress_cb: unsafe extern "C" fn(ctx: *mut c_void, u64, u64),
        on_deleted_cb: unsafe extern "C" fn(ctx: *mut c_void),
    ) -> c::Result<u64>;

    pub fn ditto_cancel_resolve_attachment(
        ditto: &'_ Ditto,
        id: c_slice::Ref<'_, u8>,
        cancel_token: u64,
    ) -> c_uint;

    pub type FfiError: Send + Sync;
    #[drop(to impl FfiDropBox for FfiError)]
    pub fn dittoffi_error_free(_error: c::Box<FfiError>);
    pub fn dittoffi_error_code(_error: &'_ FfiError) -> FfiErrorCode;

    pub fn dittoffi_error_description(_error: &'_ FfiError) -> char_p::Box;

    pub fn ditto_error_message() -> Option<char_p::Box>;

    pub type Document: Send + Sync;
    #[drop(to impl FfiDropBox for Document)]
    pub fn ditto_document_free(document: c::Box<Document>);

    pub type AttachmentHandle: Send + Sync;
    #[drop(to impl FfiDropBox for AttachmentHandle)]
    pub fn ditto_free_attachment_handle(handle: c::Box<AttachmentHandle>);

    pub type MdnsPlatformHandle: Send + Sync;
    #[drop(to impl FfiDropBox for MdnsPlatformHandle)]
    pub fn ditto_mdns_transport_free_handle(handle: c::Box<MdnsPlatformHandle>);

    pub type BlePlatformHandle: Send + Sync;
    #[drop(to impl FfiDropBox for BlePlatformHandle)]
    pub fn ditto_ble_transport_free_handle(handle: c::Box<BlePlatformHandle>);

    pub fn ditto_free_indices(indices: Option<c_slice::Box<usize>>);

    pub fn ditto_c_string_free(s: char_p::Box);

    pub fn ditto_c_bytes_free(bytes: c_slice::Box<u8>);

    pub fn ditto_identity_config_make_online_with_authentication(
        app_id: char_p::Ref<'_>,
        base_url: char_p::Ref<'_>,
    ) -> c::Result_Box<IdentityConfig>;

    pub fn ditto_identity_config_make_offline_playground(
        app_id: char_p::Ref<'_>,
        site_id: c_ulonglong,
    ) -> c::Result_Box<IdentityConfig>;

    pub fn ditto_identity_config_make_online_playground(
        app_id: char_p::Ref<'_>,
        shared_token: char_p::Ref<'_>,
        base_url: char_p::Ref<'_>,
    ) -> c::Result_Box<IdentityConfig>;

    pub fn ditto_identity_config_make_shared_key(
        app_id: char_p::Ref<'_>,
        key_der_b64: char_p::Ref<'_>,
        site_id: c_ulonglong,
    ) -> c::Result_Box<IdentityConfig>;

    pub fn ditto_identity_config_make_manual(
        manual_identity_str: char_p::Ref<'_>,
    ) -> c::Result_Box<IdentityConfig>;

    pub fn ditto_identity_config_make_manual_v0(
        config_cbor_b64: char_p::Ref<'_>,
    ) -> c::Result_Box<IdentityConfig>;

    pub fn ditto_auth_set_login_provider(
        ditto: &'_ Ditto,
        login_provider: Option<c::Box<LoginProvider>>,
    );

    pub fn ditto_auth_client_get_app_id(ditto: &'_ Ditto) -> char_p::Box;

    pub fn ditto_auth_client_user_id(ditto: &'_ Ditto) -> Option<char_p::Box>;

    pub unsafe fn ditto_auth_client_make_login_provider(
        ctx: *mut c_void,
        retain: Option<unsafe extern "C" fn(*mut c_void)>,
        release: Option<unsafe extern "C" fn(*mut c_void)>,
        expiring_cb: unsafe extern "C" fn(*mut c_void, c_uint),
    ) -> c::Box<LoginProvider>;

    pub fn ditto_auth_client_is_web_valid(ditto: &'_ Ditto) -> c_int;

    pub type FfiConnectionRequest: Send + Sync;
    #[drop(to impl FfiDropBox for FfiConnectionRequest)]
    pub fn dittoffi_connection_request_free(__: c::Box<FfiConnectionRequest>);

    pub fn dittoffi_connection_request_peer_metadata_json(
        r: &FfiConnectionRequest,
    ) -> c_slice::Ref<'_, u8>;

    pub fn dittoffi_connection_request_identity_service_metadata_json(
        r: &FfiConnectionRequest,
    ) -> c_slice::Ref<'_, u8>;

    pub fn dittoffi_connection_request_peer_key_string(r: &FfiConnectionRequest) -> char_p::Box;

    pub fn dittoffi_connection_request_authorize(
        r: &FfiConnectionRequest,
        authorized: ConnectionRequestAuthorization,
    );

    pub fn dittoffi_connection_request_connection_type(r: &FfiConnectionRequest) -> ConnectionType;

    pub fn dittoffi_presence_peer_metadata_json(ditto: &'_ Ditto) -> c_slice::Box<u8>;

    pub fn ditto_auth_client_login_with_credentials(
        ditto: &'_ Ditto,
        username: char_p::Ref<'_>,
        password: char_p::Ref<'_>,
        provider: char_p::Ref<'_>,
    ) -> c_int;

    pub fn ditto_auth_client_login_with_token_and_feedback(
        ditto: &'_ Ditto,
        token: char_p::Ref<'_>,
        provider: char_p::Ref<'_>,
    ) -> c::Result_OptionBoxedCharPtr;

    pub fn ditto_auth_client_login_with_token(
        ditto: &'_ Ditto,
        token: char_p::Ref<'_>,
        provider: char_p::Ref<'_>,
    ) -> c_int;

    pub fn ditto_auth_client_logout(ditto: &'_ Ditto) -> c_int;

    pub type LoginProvider: Send + Sync;
    #[drop(to impl FfiDropBox for LoginProvider)]
    pub fn ditto_auth_login_provider_free(login_provider: c::Box<LoginProvider>);

    pub fn ditto_set_device_name(ditto: &'_ Ditto, device_name: char_p::Ref<'_>) -> char_p::Box;

    pub unsafe fn ditto_register_presence_callback_v3(
        ditto: &'_ Ditto,
        ctx: *mut c_void,
        retain: Option<unsafe extern "C" fn(*mut c_void)>,
        release: Option<unsafe extern "C" fn(*mut c_void)>,
        c_cb: Option<unsafe extern "C" fn(*mut c_void, json: char_p::Ref<'_>)>,
    );

    pub fn ditto_presence_v3(ditto: &'_ Ditto) -> char_p::Box;

    pub type DiskUsageObserver: Send + Sync;
    #[drop(to impl FfiDropBox for DiskUsageObserver)]
    pub fn ditto_release_disk_usage_callback(_handle: c::Box<DiskUsageObserver>);

    pub unsafe fn ditto_register_disk_usage_callback(
        ditto: &'_ Ditto,
        component: FsComponent,
        ctx: *mut c_void,
        retain: Option<unsafe extern "C" fn(*mut c_void)>,
        release: Option<unsafe extern "C" fn(*mut c_void)>,
        c_cb: Option<unsafe extern "C" fn(*mut c_void, cbor: c_slice::Ref<'_, u8>)>,
    ) -> Option<c::Box<DiskUsageObserver>>;

    pub fn ditto_disk_usage(ditto: &'_ Ditto, path: FsComponent) -> c_slice::Box<u8>;

    pub type FfiQueryResultItem: Send + Sync;
    pub fn dittoffi_query_result_item_cbor(_dql_result: &'_ FfiQueryResultItem)
        -> c_slice::Box<u8>;
    pub fn dittoffi_query_result_item_json(_dql_result: &'_ FfiQueryResultItem) -> char_p::Box;
    pub fn dittoffi_query_result_item_new(
        cbor: c_slice::Ref<'_, u8>,
    ) -> FfiResult<c::Arc<FfiQueryResultItem>>;
    #[drop(to impl FfiDropArc for FfiQueryResultItem)]
    pub fn dittoffi_query_result_item_free(_dql_result: c::Arc<FfiQueryResultItem>);

    pub type FfiQueryResult: Send + Sync;
    pub fn dittoffi_query_result_item_count(response: &'_ FfiQueryResult) -> usize;
    pub fn dittoffi_query_result_item_at(
        response: &'_ FfiQueryResult,
        index: usize,
    ) -> c::Arc<FfiQueryResultItem>;
    pub fn dittoffi_query_result_mutated_document_id_count(response: &'_ FfiQueryResult) -> usize;
    pub fn dittoffi_query_result_mutated_document_id_at(
        response: &'_ FfiQueryResult,
        index: usize,
    ) -> c_slice::Box<u8>;
    pub fn dittoffi_query_result_has_commit_id(response: &'_ FfiQueryResult) -> bool;
    pub fn dittoffi_query_result_commit_id(response: &'_ FfiQueryResult) -> u64;
    #[drop(to impl FfiDropBox for FfiQueryResult)]
    pub fn dittoffi_query_result_free(_dql_reponse: c::Box<FfiQueryResult>);

    pub type FfiDiffer: Send + Sync;
    pub fn dittoffi_differ_new() -> c::Box<FfiDiffer>;
    pub fn dittoffi_differ_new_with_identity_key_paths_throws<'a>(
        identity_key_paths: c_slice::Ref<'a, char_p::Ref<'a>>,
    ) -> FfiResult<c::Box<FfiDiffer>>;
    #[drop(to impl FfiDropBox for FfiDiffer)]
    pub fn dittoffi_differ_free(differ: c::Box<FfiDiffer>);
    pub fn dittoffi_differ_diff(
        differ: &'_ FfiDiffer,
        items: c_slice::Ref<'_, c::Arc<FfiQueryResultItem>>,
    ) -> FfiCborData;
    pub fn dittoffi_differ_identity_key_path_count(differ: &'_ FfiDiffer) -> usize;
    pub fn dittoffi_differ_identity_key_path_at(differ: &'_ FfiDiffer, idx: usize) -> char_p::Box;

    pub fn dittoffi_try_exec_statement(
        ditto: &'_ Ditto,
        query: char_p::Ref<'_>,
        query_args_cbor: Option<c_slice::Ref<'_, u8>>,
    ) -> c::FfiResultBox<FfiQueryResult>;

    pub unsafe fn dittoffi_try_experimental_register_change_observer_str(
        ditto: &'_ Ditto,
        query: char_p::Ref<'_>,
        query_args_cbor: Option<c_slice::Ref<'_, u8>>,
        lq_availability: LiveQueryAvailability,
        ctx: *mut c_void,
        retain: Option<unsafe extern "C" fn(*mut c_void)>,
        release: Option<unsafe extern "C" fn(*mut c_void)>,
        c_cb: unsafe extern "C" fn(ctx: *mut c_void, params: ChangeHandlerWithQueryResult),
    ) -> c::Result<i64>;

    pub fn dittoffi_try_add_sync_subscription(
        ditto: &'_ Ditto,
        query: char_p::Ref<'_>,
        query_args_cbor: Option<c_slice::Ref<'_, u8>>,
    ) -> c::FfiResult<()>;

    pub fn dittoffi_try_remove_sync_subscription(
        ditto: &'_ Ditto,
        query: char_p::Ref<'_>,
        query_args_cbor: Option<c_slice::Ref<'_, u8>>,
    ) -> c::FfiResult<()>;

    pub fn ditto_small_peer_info_get_is_enabled(ditto: &'_ Ditto) -> bool;
    pub fn ditto_small_peer_info_set_enabled(ditto: &'_ Ditto, enabled: bool);

    pub fn ditto_small_peer_info_get_sync_scope(ditto: &'_ Ditto) -> DittoSmallPeerInfoSyncScope;
    pub fn ditto_small_peer_info_set_sync_scope(
        ditto: &'_ Ditto,
        sync_scope: DittoSmallPeerInfoSyncScope,
    );

    pub fn ditto_small_peer_info_get_metadata(ditto: &'_ Ditto) -> char_p::Box;
    pub fn ditto_small_peer_info_set_metadata(ditto: &'_ Ditto, metadata: char_p::Ref<'_>) -> i32;

    pub fn dittoffi_logger_try_export_to_file_async(
        dest_path: char_p::Ref<'_>,
        completion: BoxDynFnMut1<(), FfiResult<u64>>,
    );

    pub fn dittoffi_ditto_try_set_transport_config(
        ditto: &'_ Ditto,
        transport_config: c_slice::Ref<'_, u8>,
        should_validate: bool,
    ) -> c::FfiResult<()>;

    pub fn dittoffi_ditto_try_start_sync(ditto: &'_ Ditto) -> c::FfiResult<()>;
    pub fn dittoffi_ditto_stop_sync(ditto: &'_ Ditto);
    pub fn dittoffi_ditto_is_sync_active(ditto: &'_ Ditto) -> bool;
    pub fn dittoffi_ditto_transport_config(ditto: &'_ Ditto) -> c_slice::Box<u8>;

    pub fn dittoffi_ditto_set_cloud_sync_enabled(ditto: &'_ Ditto, enabled: bool);
    pub fn dittoffi_ditto_is_activated(ditto: &'_ Ditto) -> bool;

    // === DQL Transactions ===

}

// Sync Subscriptions API
#[apply(extern_type_polyfill!)]
unsafe extern "C" {
    pub type FfiSyncSubscription: Send + Sync;
    #[drop(to impl FfiDropBox for FfiSyncSubscription)]
    pub fn dittoffi_sync_subscription_free(subscription: c::Box<FfiSyncSubscription>);
    pub fn dittoffi_sync_subscriptions_free_sparse(vec: c::Vec<c::Box<FfiSyncSubscription>>);
    pub fn dittoffi_sync_register_subscription_throws(
        ditto: &'_ Ditto,
        query: char_p::Ref<'_>,
        query_args_cbor: Option<c_slice::Ref<'_, u8>>,
    ) -> c::FfiResult<c::Box<FfiSyncSubscription>>;
    pub fn dittoffi_sync_subscriptions(ditto: &'_ Ditto) -> c::Vec<c::Box<FfiSyncSubscription>>;
    pub fn dittoffi_sync_subscription_id(handle: &FfiSyncSubscription) -> c_slice::Box<u8>;
    pub fn dittoffi_sync_subscription_cancel(handle: &FfiSyncSubscription);
    pub fn dittoffi_sync_subscription_is_cancelled(handle: &FfiSyncSubscription) -> bool;
    pub fn dittoffi_sync_subscription_query_string(handle: &FfiSyncSubscription) -> char_p::Box;
    pub fn dittoffi_sync_subscription_query_arguments_cbor(
        handle: &FfiSyncSubscription,
    ) -> Option<c_slice::Box<u8>>;
    pub fn dittoffi_sync_subscription_query_arguments_json(
        handle: &FfiSyncSubscription,
    ) -> Option<c_slice::Box<u8>>;
}

pub type FfiStore = Ditto;
pub type FfiSignalNext = dyn 'static + Send + Sync + Fn();

#[derive_ReprC(rename = "dittoffi_store_observation_handler_with_signal_next")]
#[repr(transparent)]
pub struct FfiStoreObservationHandlerWithSignalNext(
    pub  repr_c::Box<
        dyn 'static + Send + FnMut(repr_c::Box<FfiQueryResult>, repr_c::Arc<FfiSignalNext>),
    >,
);

// Store Observer API
#[apply(extern_type_polyfill!)]
unsafe extern "C" {
    pub type FfiStoreObserver: Send + Sync;
    #[drop(to impl FfiDropBox for FfiStoreObserver)]
    pub fn dittoffi_store_observer_free(handle: c::Box<FfiStoreObserver>);
    pub fn dittoffi_store_observers_free_sparse(vec: c::Vec<c::Box<FfiStoreObserver>>);
    pub fn dittoffi_store_register_observer_throws(
        ditto: &'_ Ditto,
        query: char_p::Ref<'_>,
        query_args_cbor: Option<c_slice::Ref<'_, u8>>,
        callback: FfiStoreObservationHandlerWithSignalNext,
    ) -> FfiResult<c::Box<FfiStoreObserver>>;
    pub fn dittoffi_store_observers(store: &'_ FfiStore) -> c::Vec<c::Box<FfiStoreObserver>>;
    pub fn dittoffi_store_observer_id(handle: &FfiStoreObserver) -> c_slice::Box<u8>;
    pub fn dittoffi_store_observer_cancel(handle: &FfiStoreObserver);
    pub fn dittoffi_store_observer_is_cancelled(handle: &FfiStoreObserver) -> bool;
    pub fn dittoffi_store_observer_query_string(handle: &FfiStoreObserver) -> char_p::Box;
    pub fn dittoffi_store_observer_query_arguments_cbor(
        handle: &FfiStoreObserver,
    ) -> Option<c_slice::Box<u8>>;
}

// DQL Transactions API
#[apply(extern_type_polyfill)]
unsafe extern "C" {
    pub type FfiTransaction: Send + Sync;
    #[drop(to impl FfiDropBox for FfiTransaction)]
    pub fn dittoffi_transaction_free(transaction: c::Box<FfiTransaction>);
    pub fn dittoffi_store_begin_transaction_async_throws(
        store: &'_ FfiStore,
        options: BeginTransactionOptions<'_>,
        continuation: BoxDynFnMut1<(), FfiResult<c::Box<FfiTransaction>>>,
    );
    pub fn dittoffi_transaction_complete_async_throws(
        transaction: &'_ FfiTransaction,
        action: TransactionCompletionAction,
        continuation: BoxDynFnMut1<(), FfiResult<TransactionCompletionAction>>,
    );
    pub fn dittoffi_transaction_execute_async_throws(
        transaction: &'_ FfiTransaction,
        query: char_p::Ref<'_>,
        query_args: Option<c_slice::Ref<'_, u8>>,
        continuation: BoxDynFnMut1<(), c::FfiResultBox<FfiQueryResult>>,
    );
    pub fn dittoffi_transaction_info(transaction: &'_ FfiTransaction) -> FfiCborData;
}

pub type FfiDynPresenceCallback = dyn 'static + Send + FnMut(c_slice::Box<u8>);

// Presence API (uses Presence v3 format)
#[apply(extern_type_polyfill)]
unsafe extern "C" {
    pub type FfiPresenceObserver: Send + Sync;
    #[drop(to impl FfiDropBox for FfiPresenceObserver)]
    pub fn dittoffi_presence_observer_free(observer: repr_c::Box<FfiPresenceObserver>);
    pub fn dittoffi_presence_register_observer_throws(
        ditto: &'_ Ditto,
        callback: repr_c::Box<FfiDynPresenceCallback>,
    ) -> FfiResult<repr_c::Box<FfiPresenceObserver>>;
    pub fn dittoffi_presence_observer_id(observer: &FfiPresenceObserver) -> c_slice::Box<u8>;
    pub fn dittoffi_presence_observer_cancel(observer: &FfiPresenceObserver);
    pub fn dittoffi_presence_observer_is_cancelled(observer: &FfiPresenceObserver) -> bool;
    pub fn dittoffi_presence_graph(ditto: &'_ Ditto) -> c_slice::Box<u8>;

    pub fn dittoffi_presence_peer_metadata(ditto: &'_ Ditto) -> char_p::Box;
    pub fn dittoffi_presence_set_peer_metadata_json_throws(
        ditto: &'_ Ditto,
        peer_info: c_slice::Ref<'_, u8>,
    ) -> FfiResult<()>;

    pub fn dittoffi_presence_set_connection_request_handler(
        ditto: &'_ Ditto,
        ffi_handler: Option<
            safer_ffi::dyn_traits::VirtualPtr<dyn Send + Sync + FfiConnectionRequestHandler>,
        >,
    );
}

impl fmt::Debug for Ditto {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Ditto").finish()
    }
}

impl FfiDropBox for IdentityConfig {
    unsafe fn drop_boxed(_: &mut c::Box<Self>) {
        // We don't expose this functionality in the API, since it is to be
        // given to the final `Ditto` constructor anyway
        panic!("A `c::Box<IdentityConfig>` is to be fed to `ditto_make`")
    }
}

#[repr(C)]
pub enum ConnectionRequestAuthorization {
    Deny,

    Allow,
}

#[non_exhaustive]
#[repr(C)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Platform {
    Windows,
    Mac,
    Ios,
    Tvos,
    Android,
    Linux,
    Unknown,
}

#[::safer_ffi::derive_ReprC]
#[repr(C)]
pub struct FfiResult<T: ::safer_ffi::layout::ReprC> {
    pub error: Option<c::Box<FfiError>>,
    pub success: T::CLayout,
}

impl fmt::Debug for FfiError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("FfiError").finish()
    }
}

// These are needed because `FfiResult` stores a `T::CLayout` rather than a `T`. For opaque types
// (such as `FfiTransaction`), this is a `*mut u8`, which doesn't implement `Send` or `Sync`.
//
// But this `!Send`-ness and `!Sync`-ness is not 100% "real", in a sense. There's nothing about a
// `*mut u8` that prevents it being sent across threads - rather it's a design decision to force
// people who are likely writing unsafe code to think about whether their `*mut T`-containing
// struct should implement `Send` or `Sync`
//
// In our case, this is overly cautious, so we provide blanket impls for `FfiResult<T>`
unsafe impl<T: ::safer_ffi::layout::ReprC> Send for FfiResult<T> where T: Send {}
unsafe impl<T: ::safer_ffi::layout::ReprC> Sync for FfiResult<T> where T: Sync {}

// Hack: we use a temporary rename as `CoreApiErrorKind`
// so that rustdoc shows the associated items under that name.
pub use CoreApiErrorKind as FfiErrorCode;
#[apply(crate::ffi_utils::non_exhaustive_ffi_enum!)]
#[repr(i32)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum CoreApiErrorKind {
    // Note(Daniel): keep up-to-date with `ffi/src/result/error.rs`!
    ActivationLicenseTokenExpired,
    ActivationLicenseTokenInvalid,
    ActivationLicenseUnsupportedFutureVersion,
    ActivationNotActivated,
    ActivationUnnecessary,
    AuthenticationExpirationHandlerMissing,
    Base64Invalid,
    CborInvalid,
    CborUnsupported,
    Crdt,
    DifferIdentityKeyPathInvalid,
    DqlEvaluationError,
    DqlInvalidQueryArgs,
    DqlQueryCompilation,
    DqlUnsupported,
    EncryptionExtraneousPassphraseGiven,
    EncryptionPassphraseInvalid,
    EncryptionPassphraseNotGiven,
    JsFloatingStoreOperation,
    IoAlreadyExists,
    IoNotFound,
    IoOperationFailed,
    IoPermissionDenied,
    LockedDittoWorkingDirectory,
    ParameterQuery,
    StoreDatabase,
    StoreDocumentId,
    StoreDocumentNotFound,
    StoreQuery,
    StoreTransactionReadOnly,
    Transport,
    Unsupported,
    ValidationDepthLimitExceeded,
    ValidationInvalidCbor,
    ValidationInvalidJson,
    ValidationInvalidTransportConfig,
    ValidationInvalidDittoConfig,
    ValidationNotAMap,
    ValidationSizeLimitExceeded,
    Unknown,
    Internal,
    // _NonExhaustive /* handled properly by `non_exhaustive_ffi_enum!` */
}

// Keep in sync with ffi/src/presence.rs (and core/presence/src/types.rs)
crate::ffi_utils::non_exhaustive_ffi_enum! {
    #[repr(i32)]
    #[derive(Clone, PartialEq, Eq)]
    pub enum ConnectionType {
        Bluetooth,
        AccessPoint,
        P2PWiFi,
        WebSocket,
    }
}

#[repr(C)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DittoErrorCode {
    Ok = 0x0000000,
    Unknown = 0x0000001,
    NotImplemented = 0x0000002,
    // global ditto errors =    0x1000000,
    FailedToAcquireLockFile = 0x1000001,
    // Encryption errors =      0x2000000,
    InvalidPassphrase = 0x2000001,
    ExtraneousPassphraseGiven = 0x2000002,
    PassphraseNotGiven = 0x2000003,
    AlreadyEncrypted = 0x2000004,
    EncryptionFailed = 0x2000005,
    CannotBeEncrypted = 0x2000006,
    NotInitialized = 0x2000007,
}

#[non_exhaustive]
#[repr(C)]
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(dead_code)]
pub enum Language {
    Swift,
    ObjectiveC,
    CPlusPlus,
    CSharp,
    Javascript,
    Unknown,
    Rust,
}

#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum CLogLevel {
    // Starts at 1 to match the Rust log levels which have an `Off` case with
    // value 0
    Error = 1,
    Warning,
    Info,
    Debug,
    Verbose,
}

/// A custom logging callback to intercept all logs emitted from the Ditto SDK.
///
/// This is a transparent wrapper around an `extern "C"` function pointer that
/// receives log events from the Ditto core.
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct CustomLogCb(pub extern "C" fn(CLogLevel, char_p::Box));

#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)]
#[allow(dead_code)]
pub enum LicenseVerificationResult {
    LicenseOk = 0,
    VerificationFailed = -1,
    LicenseExpired = -2,
    UnsupportedFutureVersion = -3,
}

#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum WriteStrategyRs {
    Merge,
    InsertIfAbsent,
    InsertDefaultIfAbsent,
}

#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum FsComponent {
    Root = 0,
    Store,
    Auth,
    Replication,
    Attachment,
}

#[derive(Debug, Clone)]
#[repr(C)]
pub struct COrderByParam<'query> {
    pub query_c_str: char_p::Ref<'query>,
    pub direction: QuerySortDirection,
}
impl NonOpaque for COrderByParam<'_> {}

#[derive(Clone, Debug, PartialEq)]
#[derive_ReprC(js, rename = "dittoffi_transaction_options")]
#[repr(C)]
pub struct FfiTransactionOptions {
    /// Whether the transaction is read-only.
    pub is_read_only: bool,

    /// An optional hint for the transaction. This is often useful for identifying a transaction in
    /// logs.
    pub hint: Option<char_p::Box>,
}

/// FFI appropriate representation of the options available when beginning a transaction.
#[derive(Clone, Debug, Default, PartialEq)]
#[derive_ReprC(js, rename = "dittoffi_store_begin_transaction_options")]
#[repr(C)]
pub struct BeginTransactionOptions<'hint> {
    /// Whether the transaction is read-only. Defaults to `false`.
    pub is_read_only: bool,

    /// An optional hint for the transaction. This is often useful for identifying a transaction in
    /// logs.
    pub hint: Option<char_p::Ref<'hint>>,
}

// inner module to reduce scope of `#[allow(deprecated)]` (required for saferffi c-style enums)
// saferffi generated warning about C style enum triggers a new lint in rust 1.91+
pub use transaction_completion::*;
#[allow(deprecated, dead_code)]
mod transaction_completion {
    #[derive(Debug, Clone, Copy)]
    #[repr(C)]
    #[::safer_ffi::derive_ReprC]
    pub enum TransactionCompletionAction {
        Commit = 0,
        Rollback = 1,
    }
}

#[derive_ReprC(js, rename = "dittoffi_cbor_data")]
#[repr(transparent)]
// TODO(cameron): is this the same?
pub struct FfiCborData(pub c_slice::Box<u8>);

#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[allow(dead_code)]
pub enum QuerySortDirection {
    Ascending = 1,
    Descending,
}

#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DittoCrdtType {
    Counter = 0,
    Register = 1,
    Attachment = 2,
    Rga = 3,
    RWMap = 4,
}

#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum PathAccessorType {
    String,
    Number,
    Int,
    UInt,
    Float,
    Double,
    Bool,
    Null,
    Object,
    Array,
    Any,

    // CRDT types
    Counter,
    Register,
    Attachment,
    // This isn't ever used as things stand, but is here for completeness and to
    // make things easier in future if we do want to support it
    RWMap,
}

impl fmt::Debug for Document {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let c_str = ditto_document_id(self);
        // TODO: FIXME
        f.debug_struct("Document").field("id", &c_str).finish()
    }
}

#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[allow(dead_code)]
pub enum LiveQueryAvailability {
    Always,
    WhenSignalled,
}

#[repr(C)]
#[allow(nonstandard_style)]
pub struct ChangeHandlerWithDocsDiff {
    /// The vec must be freed with `ditto_sparse_vec_documents_free`. The `Document`s contained by
    /// the vec are owned by the recipient of the diff and should be freed independently of the
    /// vec.
    pub documents: c::Vec<c::Box<Document>>,
    pub is_initial: bool,
    /// The vec, if present, must be freed with `ditto_sparse_vec_documents_free`. The `Document`s
    /// contained by the vec are owned by the recipient of the diff and should be freed
    /// independently of the vec.
    pub old_documents: Option<c::Vec<c::Box<Document>>>,
    /// Must be freed using `ditto_free_indices`.
    pub insertions: Option<c_slice::Box<usize>>,
    /// Must be freed using `ditto_free_indices`.
    pub deletions: Option<c_slice::Box<usize>>,
    /// Must be freed using `ditto_free_indices`.
    pub updates: Option<c_slice::Box<usize>>,
    /// Must be freed using `ditto_free_indices`.
    pub moves: Option<c_slice::Box<usize>>,
}

#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[allow(dead_code)]
pub enum AttachmentFileOperation {
    Copy = 1,
    Move,
}

#[repr(C)]
pub struct Attachment {
    pub id: c_slice::Box<u8>,
    pub len: u64,
    pub handle: c::Box<AttachmentHandle>,
}

impl fmt::Debug for AttachmentHandle {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("AttachmentHandle").finish()
    }
}

#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum StringPrimitiveFormat {
    WithQuotes,
    WithoutQuotes,
}

#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
/// Which peers to replicate the `__small_peer_info` collection to.
pub enum DittoSmallPeerInfoSyncScope {
    BigPeerOnly,
    LocalPeerOnly,
}

pub trait IntoRustResult {
    type Ok;

    fn into_rust_result(_: Self) -> Result<Self::Ok, ::core::num::NonZeroI32>;
}

#[derive_ReprC]
#[repr(C)]
pub struct ChangeHandlerWithQueryResult {
    /// Must be freed with `dittoffi_query_result_free`.
    pub query_result: c::Box<FfiQueryResult>,
}

#[derive_ReprC(dyn)]
pub trait FfiFnMut {
    fn call(&mut self);
}

impl<F: FnMut()> FfiFnMut for F {
    fn call(&mut self) {
        self()
    }
}

// Note(Daniel): keep up-to-date with `ffi/src/auth/mod.rs`
#[derive_ReprC(dyn, Clone)]
pub trait FfiConnectionRequestHandler: 'static + Send + Sync {
    fn on_connecting(&self, connection_request: c::Box<FfiConnectionRequest>);
}

impl<F> FfiConnectionRequestHandler for F
where
    F: 'static + Send + Sync + Fn(c::Box<FfiConnectionRequest>),
{
    fn on_connecting(&self, connection_request: c::Box<FfiConnectionRequest>) {
        self(connection_request)
    }
}

#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)]
// Keep up-to-date with `ffi/src/result/error/transport.rs`
pub enum DittoSdkTransportsError {
    None = 0,
    Generic = 1,
    Unavailable = 2,
    MissingBluetoothInfoPlistEntry = 3,
    MissingBluetoothUIBackgroundModesInfoPlistEntry = 4,
}

#[cfg(feature = "preview-datastreams")]
#[apply(extern_type_polyfill!)]
unsafe extern "C" {
    pub fn dittoffi_get_preview_datastreams(
        ditto: &Ditto,
    ) -> ::dittolive_ditto_base::datastreams::Endpoint;
}
