// SPDX-License-Identifier: Apache-2.0
//! Single source of truth for the FSKit C ABI surface.
//!
//! Every callback typedef, opaque handle, and extern function in the
//! Swift-side bridging header (`swift/HeddleFSKit/HeddleFSKit-Bridging.h`)
//! is derived from the declarations in *this* file. The header is
//! generated by `cbindgen` at build time (see `build.rs`). The Swift
//! source then `#imports` that generated header — so any change here
//! propagates to Swift on the next `cargo build --features fskit`,
//! and a drift between the Rust ABI and the Swift call sites becomes
//! a `swiftc` error rather than runtime UB.
//!
//! Conventions for adding to this surface:
//!   * Use `pub type` aliases for callback function pointer types.
//!     Wrap them in `Option<unsafe extern "C" fn(...) -> i32>` so the
//!     Rust caller can pass `None` and the C side sees `NULL`.
//!   * Use `i32` (not `c_int`) for return codes so cbindgen emits
//!     `int32_t` rather than `int`. Errno-style: 0 success, libc errno
//!     on failure.
//!   * Use `u64`/`u32`/`u8` for sized integers (cbindgen emits
//!     `uint64_t`/`uint32_t`/`uint8_t`).
//!   * Keep the `unsafe extern "C"` block at the bottom — cbindgen
//!     emits prototypes for these in declaration order.

use std::{
    ffi::{CStr, c_char, c_void},
    ptr,
};

use tracing::warn;

// ----------------------------------------------------------------
// Opaque handle
// ----------------------------------------------------------------

/// Opaque pointer to the Swift-side `HeddleFSKitSession`. Treat as
/// untyped on the Rust side; the Swift session walks behind it.
pub type HeddleFSKitSessionHandle = *mut c_void;

// ----------------------------------------------------------------
// Callback typedefs — invoked by Swift, implemented by Rust.
// ----------------------------------------------------------------

/// Resolve `name` inside `parent_inode`. Writes the child's identity
/// into the out-pointers when found; returns 0 on success, `ENOENT`
/// when missing, or another libc errno on failure.
pub type HeddleLookupCallback = Option<
    unsafe extern "C" fn(
        user_data: *mut c_void,
        parent_inode: u64,
        name_utf8: *const c_char,
        out_child_inode: *mut u64,
        out_unix_mode: *mut u32,
        out_size: *mut u64,
    ) -> i32,
>;

/// Fetch attributes for `inode`. Mode includes type bits (S_IFREG /
/// S_IFDIR / etc.). `out_mtime_sec` receives the modification time
/// as seconds-since-UNIX-epoch — zero is a valid value (means
/// "epoch"), so callers should treat the out-pointer as the only
/// signal of success.
pub type HeddleGetattrCallback = Option<
    unsafe extern "C" fn(
        user_data: *mut c_void,
        inode: u64,
        out_unix_mode: *mut u32,
        out_size: *mut u64,
        out_nlink: *mut u32,
        out_mtime_sec: *mut i64,
    ) -> i32,
>;

/// Read `[offset, offset+buffer_capacity)` from `inode` into `buffer`.
/// Writes actual bytes-read into `out_bytes_read`.
pub type HeddleReadCallback = Option<
    unsafe extern "C" fn(
        user_data: *mut c_void,
        inode: u64,
        offset: u64,
        buffer: *mut u8,
        buffer_capacity: u64,
        out_bytes_read: *mut u64,
    ) -> i32,
>;

/// Write `[data, data+data_len)` into `inode` at `offset`. Writes
/// actual bytes-written into `out_bytes_written`.
pub type HeddleWriteCallback = Option<
    unsafe extern "C" fn(
        user_data: *mut c_void,
        inode: u64,
        offset: u64,
        data: *const u8,
        data_len: u64,
        out_bytes_written: *mut u64,
    ) -> i32,
>;

/// Per-entry callback invoked from inside [`HeddleEnumerateCallback`].
/// Return 0 to keep iterating, non-zero to stop early (typically when
/// the FSKit reply buffer is full). `mtime_sec` is the modification
/// time of the entry as seconds-since-UNIX-epoch; for content-addressed
/// mounts every entry currently shares the mount's bootstrap time.
pub type HeddleEnumerateEmit = Option<
    unsafe extern "C" fn(
        emit_user_data: *mut c_void,
        child_inode: u64,
        child_name_utf8: *const c_char,
        unix_mode: u32,
        size: u64,
        mtime_sec: i64,
    ) -> i32,
>;

/// Enumerate the children of `dir_inode`. The Rust side calls
/// `emit(emit_user_data, ...)` once per entry.
pub type HeddleEnumerateCallback = Option<
    unsafe extern "C" fn(
        user_data: *mut c_void,
        dir_inode: u64,
        emit_user_data: *mut c_void,
        emit: HeddleEnumerateEmit,
    ) -> i32,
>;

/// Flush any buffered writes for `inode`. Heddle's mount is read-only
/// today so this currently always returns 0.
pub type HeddleFlushCallback =
    Option<unsafe extern "C" fn(user_data: *mut c_void, inode: u64) -> i32>;

/// Release the `user_data` payload. Called exactly once when the
/// Swift session is freed; reclaims the boxed `PlatformShell`.
pub type HeddleDropCallback = Option<unsafe extern "C" fn(user_data: *mut c_void)>;

// ----------------------------------------------------------------
// Functions exported by the Swift side, called by Rust.
//
// These are imports for Rust, but cbindgen still emits prototypes
// for them in the generated C header (which is what Swift consumes).
// ----------------------------------------------------------------

unsafe extern "C" {
    /// Construct a new session. Returns `NULL` on allocation failure.
    /// The Swift side retains the session; release it with
    /// [`heddle_fskit_session_free`].
    pub fn heddle_fskit_session_new(
        user_data: *mut c_void,
        lookup: HeddleLookupCallback,
        getattr: HeddleGetattrCallback,
        read: HeddleReadCallback,
        write: HeddleWriteCallback,
        enumerate: HeddleEnumerateCallback,
        flush: HeddleFlushCallback,
        drop: HeddleDropCallback,
    ) -> HeddleFSKitSessionHandle;

    /// Release the session. Triggers the `drop` callback exactly once.
    pub fn heddle_fskit_session_free(handle: HeddleFSKitSessionHandle);

    /// Returns 1 when the host can load FSKit (macOS 15.4+), else 0.
    pub fn heddle_fskit_is_available() -> i32;
}

// ----------------------------------------------------------------
// High-level entry point used by the System Extension.
//
// `heddle_fskit_open_thread` is the C ABI Swift-extension code calls
// to bootstrap a mount session: it parses the repo path + thread
// name into Rust types, opens the repository, builds a
// `ContentAddressedMount`, and hands back the same session handle
// shape `FSKitShell::new(mount)` produces in-process. The Swift
// caller dispatches per-syscall through the callbacks held on the
// returned session, and releases the handle via
// [`heddle_fskit_session_free`].
//
// Implementation lives in `super::open_thread` so it has visibility
// into the trampolines + `FSKitShell::into_handle`. This shim is
// only here so cbindgen (which only scans this file) emits the
// prototype in the generated bridging header.
// ----------------------------------------------------------------

/// Open a content-addressed mount for `thread_id_utf8` rooted at
/// `repo_path_utf8`. Returns a session handle suitable for the
/// FSKit `FSVolume` to dispatch through, or NULL if any step
/// fails (bad UTF-8, missing repo, unknown thread).
///
/// The caller owns the returned handle and must release it with
/// [`heddle_fskit_session_free`] when the volume is torn down.
///
/// # Safety
/// Both arguments must be NUL-terminated UTF-8 C strings owned by
/// the caller for the duration of the call.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn heddle_fskit_open_thread(
    repo_path_utf8: *const c_char,
    thread_id_utf8: *const c_char,
) -> HeddleFSKitSessionHandle {
    if repo_path_utf8.is_null() || thread_id_utf8.is_null() {
        return ptr::null_mut();
    }
    let repo_path = match unsafe { CStr::from_ptr(repo_path_utf8) }.to_str() {
        Ok(s) => s,
        Err(e) => {
            warn!("heddle_fskit_open_thread: repo_path not UTF-8: {e}");
            return ptr::null_mut();
        }
    };
    let thread_id = match unsafe { CStr::from_ptr(thread_id_utf8) }.to_str() {
        Ok(s) => s,
        Err(e) => {
            warn!("heddle_fskit_open_thread: thread_id not UTF-8: {e}");
            return ptr::null_mut();
        }
    };
    super::open_thread(repo_path, thread_id)
}
