//! quicproquo server plugin API — C-ABI vtable.
//!
//! # Overview
//!
//! Every plugin is a `cdylib` that exports one symbol:
//!
//! ```c
//! extern "C" int32_t qpq_plugin_init(HookVTable *vtable);
//! ```
//!
//! The server passes a zeroed [`HookVTable`] to `qpq_plugin_init`.  The plugin
//! fills in whichever function pointers it cares about and returns `0` on
//! success or a negative error code on failure.  Unused slots remain null and
//! the server treats them as no-ops.
//!
//! # Wire types
//!
//! All event structs are passed by const pointer across the FFI boundary.  The
//! server owns the memory; plugin code must not store these pointers beyond the
//! duration of the callback.
//!
//! # Thread safety
//!
//! Hook callbacks are called from the Tokio worker thread that handles the RPC.
//! Plugins must be `Send + Sync` in practice (the server wraps them in `Arc`).
//! Global plugin state should be guarded with `Mutex` or `RwLock` if mutable.
//!
//! # Return values
//!
//! Hooks that can reject an operation return [`HookResult`].  The server maps
//! `HOOK_CONTINUE` to `HookAction::Continue` and any other value to
//! `HookAction::Reject` with the reason string from [`HookVTable::error_message`].

#![no_std]

/// Plugin init returned success.
pub const PLUGIN_OK: i32 = 0;

/// Hook should allow the operation to proceed.
pub const HOOK_CONTINUE: i32 = 0;

/// Hook wants to reject the operation.  Fill [`HookVTable::error_message`] with
/// a null-terminated reason before returning this.
pub const HOOK_REJECT: i32 = 1;

// ── Event structs (C-compatible) ─────────────────────────────────────────────

/// Event data for message enqueue operations.
///
/// Passed by pointer to [`HookVTable::on_message_enqueue`].
/// All pointer fields are valid for the duration of the callback only.
#[repr(C)]
pub struct CMessageEvent {
    /// Sender's Ed25519 identity key (32 bytes), or null if sealed sender.
    pub sender_identity: *const u8,
    /// Length of `sender_identity`; 0 when null.
    pub sender_identity_len: usize,
    /// Recipient's Ed25519 identity key (32 bytes).
    pub recipient_key: *const u8,
    pub recipient_key_len: usize,
    /// Channel ID (16 bytes).
    pub channel_id: *const u8,
    pub channel_id_len: usize,
    /// Length of the encrypted payload.
    pub payload_len: usize,
    /// Server-assigned sequence number.
    pub seq: u64,
}

/// Event data for authentication operations.
#[repr(C)]
pub struct CAuthEvent {
    /// Null-terminated username string.
    pub username: *const u8,
    pub username_len: usize,
    /// Non-zero on success.
    pub success: i32,
    /// Null-terminated failure reason (empty on success).
    pub failure_reason: *const u8,
    pub failure_reason_len: usize,
}

/// Event data for channel creation operations.
#[repr(C)]
pub struct CChannelEvent {
    pub channel_id: *const u8,
    pub channel_id_len: usize,
    pub initiator_key: *const u8,
    pub initiator_key_len: usize,
    pub peer_key: *const u8,
    pub peer_key_len: usize,
    /// Non-zero if this is a freshly created channel.
    pub was_new: i32,
}

/// Event data for message fetch operations.
#[repr(C)]
pub struct CFetchEvent {
    pub recipient_key: *const u8,
    pub recipient_key_len: usize,
    pub channel_id: *const u8,
    pub channel_id_len: usize,
    pub message_count: usize,
}

// ── HookVTable ────────────────────────────────────────────────────────────────

/// C-ABI function-pointer table filled by [`qpq_plugin_init`].
///
/// All fields default to null (no-op).  The server only calls a slot when its
/// pointer is non-null.  The `user_data` field is passed as the first argument
/// to every hook; use it to thread plugin-private state without global variables.
#[repr(C)]
pub struct HookVTable {
    /// Opaque pointer to plugin-private state. The server passes this as the
    /// first argument to every hook callback. May be null.
    pub user_data: *mut core::ffi::c_void,

    /// Called before a message is stored. Return [`HOOK_CONTINUE`] or
    /// [`HOOK_REJECT`]. On reject, populate `error_message`.
    pub on_message_enqueue: Option<
        unsafe extern "C" fn(
            user_data: *mut core::ffi::c_void,
            event: *const CMessageEvent,
        ) -> i32,
    >,

    /// Called after a batch of messages is enqueued (fire-and-forget, no return value).
    pub on_batch_enqueue: Option<
        unsafe extern "C" fn(
            user_data: *mut core::ffi::c_void,
            events: *const CMessageEvent,
            count: usize,
        ),
    >,

    /// Called after a login attempt (fire-and-forget).
    pub on_auth: Option<
        unsafe extern "C" fn(
            user_data: *mut core::ffi::c_void,
            event: *const CAuthEvent,
        ),
    >,

    /// Called after a channel is created or looked up (fire-and-forget).
    pub on_channel_created: Option<
        unsafe extern "C" fn(
            user_data: *mut core::ffi::c_void,
            event: *const CChannelEvent,
        ),
    >,

    /// Called after messages are fetched (fire-and-forget).
    pub on_fetch: Option<
        unsafe extern "C" fn(
            user_data: *mut core::ffi::c_void,
            event: *const CFetchEvent,
        ),
    >,

    /// Called after a user completes OPAQUE registration (fire-and-forget).
    pub on_user_registered: Option<
        unsafe extern "C" fn(
            user_data: *mut core::ffi::c_void,
            username: *const u8,
            username_len: usize,
            identity_key: *const u8,
            identity_key_len: usize,
        ),
    >,

    /// When a hook returns [`HOOK_REJECT`], the server calls this to retrieve
    /// the null-terminated rejection reason. The returned pointer must remain
    /// valid until the next call on the same `user_data`. May be null (server
    /// will use a generic message).
    pub error_message: Option<
        unsafe extern "C" fn(user_data: *mut core::ffi::c_void) -> *const u8,
    >,

    /// Called by the server when it is done with this plugin (shutdown).
    /// Release resources / join threads here. May be null.
    pub destroy: Option<unsafe extern "C" fn(user_data: *mut core::ffi::c_void)>,
}

// SAFETY: `HookVTable` contains raw pointers (`user_data`, function pointers)
// which are not inherently `Send`/`Sync`. These impls are sound because:
//
// 1. `user_data` is an opaque pointer managed entirely by the plugin. The plugin
//    contract (documented in the module-level doc comment) requires that plugins
//    use interior mutability (Mutex/RwLock) if `user_data` is mutated through
//    callbacks. The server wraps each loaded plugin in an `Arc<HookVTable>` and
//    may invoke hooks from any Tokio worker thread.
//
// 2. All function pointers are `unsafe extern "C" fn` — they are plain addresses
//    with no captured state. The code they point to must be thread-safe per the
//    plugin contract.
//
// 3. The server guarantees that `destroy` is called exactly once during shutdown,
//    after which no further hook calls are made on the vtable.
#[allow(unsafe_code)]
unsafe impl Send for HookVTable {}
#[allow(unsafe_code)]
unsafe impl Sync for HookVTable {}
