//! C-ABI Foreign Function Interface for Syna database.
//!
//! This module provides extern "C" functions for cross-language access.
//! All functions use `catch_unwind` to prevent Rust panics from unwinding
//! into foreign code.

// FFI functions intentionally take raw pointers without being marked unsafe
// because they handle null checks and use catch_unwind for safety
#![allow(clippy::not_unsafe_ptr_arg_deref)]
// Using slice::from_raw_parts_mut with Box::from_raw is intentional for FFI memory management
#![allow(clippy::cast_slice_from_raw_parts)]

use std::ffi::CStr;
use std::os::raw::c_char;

use crate::engine::{close_db, free_tensor, open_db, with_db};
use crate::error::{
    ERR_GENERIC, ERR_INTERNAL_PANIC, ERR_INVALID_PATH, ERR_KEY_NOT_FOUND, ERR_SUCCESS,
    ERR_TYPE_MISMATCH,
};
use crate::types::Atom;

/// Opens a database at the given path and registers it in the global registry.
///
/// # Arguments
/// * `path` - Null-terminated C string containing the path to the database file
///
/// # Returns
/// * `1` (ERR_SUCCESS) - Database opened successfully or was already open
/// * `0` (ERR_GENERIC) - Generic error during database open
/// * `-2` (ERR_INVALID_PATH) - Path is null or invalid UTF-8
/// * `-100` (ERR_INTERNAL_PANIC) - Internal panic occurred
///
/// # Safety
/// * `path` must be a valid null-terminated C string or null
///
/// _Requirements: 6.2, 6.3, 9.4_
#[no_mangle]
pub extern "C" fn SYNA_open(path: *const c_char) -> i32 {
    std::panic::catch_unwind(|| {
        // Check for null pointer
        if path.is_null() {
            return ERR_INVALID_PATH;
        }

        // Convert C string to Rust string
        let c_str = unsafe { CStr::from_ptr(path) };
        let path_str = match c_str.to_str() {
            Ok(s) => s,
            Err(_) => return ERR_INVALID_PATH,
        };

        // Open the database
        match open_db(path_str) {
            Ok(_) => ERR_SUCCESS,
            Err(_) => ERR_GENERIC,
        }
    })
    .unwrap_or(ERR_INTERNAL_PANIC)
}

/// Closes a database and removes it from the global registry.
///
/// # Arguments
/// * `path` - Null-terminated C string containing the path to the database file
///
/// # Returns
/// * `1` (ERR_SUCCESS) - Database closed successfully
/// * `0` (ERR_GENERIC) - Generic error during database close
/// * `-1` (ERR_DB_NOT_FOUND) - Database not found in registry
/// * `-2` (ERR_INVALID_PATH) - Path is null or invalid UTF-8
/// * `-100` (ERR_INTERNAL_PANIC) - Internal panic occurred
///
/// # Safety
/// * `path` must be a valid null-terminated C string or null
///
/// _Requirements: 6.2_
#[no_mangle]
pub extern "C" fn SYNA_close(path: *const c_char) -> i32 {
    std::panic::catch_unwind(|| {
        // Check for null pointer
        if path.is_null() {
            return ERR_INVALID_PATH;
        }

        // Convert C string to Rust string
        let c_str = unsafe { CStr::from_ptr(path) };
        let path_str = match c_str.to_str() {
            Ok(s) => s,
            Err(_) => return ERR_INVALID_PATH,
        };

        // Close the database
        match close_db(path_str) {
            Ok(_) => ERR_SUCCESS,
            Err(crate::error::SynaError::NotFound(_)) => crate::error::ERR_DB_NOT_FOUND,
            Err(_) => ERR_GENERIC,
        }
    })
    .unwrap_or(ERR_INTERNAL_PANIC)
}

/// Writes a float value to the database.
///
/// # Arguments
/// * `path` - Null-terminated C string containing the path to the database file
/// * `key` - Null-terminated C string containing the key
/// * `value` - The f64 value to store
///
/// # Returns
/// * Positive value - The byte offset where the entry was written
/// * `-1` (ERR_DB_NOT_FOUND) - Database not found in registry
/// * `-2` (ERR_INVALID_PATH) - Path or key is null or invalid UTF-8
/// * `-100` (ERR_INTERNAL_PANIC) - Internal panic occurred
///
/// # Safety
/// * `path` and `key` must be valid null-terminated C strings or null
///
/// _Requirements: 6.2, 6.3_
#[no_mangle]
pub extern "C" fn SYNA_put_float(path: *const c_char, key: *const c_char, value: f64) -> i64 {
    std::panic::catch_unwind(|| {
        // Validate pointers
        if path.is_null() || key.is_null() {
            return ERR_INVALID_PATH as i64;
        }

        // Convert C strings to Rust strings
        let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
            Ok(s) => s,
            Err(_) => return ERR_INVALID_PATH as i64,
        };

        let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
            Ok(s) => s,
            Err(_) => return ERR_INVALID_PATH as i64,
        };

        // Call with_db to append the value
        match with_db(path_str, |db| db.append(key_str, Atom::Float(value))) {
            Ok(offset) => offset as i64,
            Err(crate::error::SynaError::NotFound(_)) => crate::error::ERR_DB_NOT_FOUND as i64,
            Err(_) => ERR_GENERIC as i64,
        }
    })
    .unwrap_or(ERR_INTERNAL_PANIC as i64)
}

/// Writes an integer value to the database.
///
/// # Arguments
/// * `path` - Null-terminated C string containing the path to the database file
/// * `key` - Null-terminated C string containing the key
/// * `value` - The i64 value to store
///
/// # Returns
/// * Positive value - The byte offset where the entry was written
/// * `-1` (ERR_DB_NOT_FOUND) - Database not found in registry
/// * `-2` (ERR_INVALID_PATH) - Path or key is null or invalid UTF-8
/// * `-100` (ERR_INTERNAL_PANIC) - Internal panic occurred
///
/// # Safety
/// * `path` and `key` must be valid null-terminated C strings or null
///
/// _Requirements: 6.2, 6.3_
#[no_mangle]
pub extern "C" fn SYNA_put_int(path: *const c_char, key: *const c_char, value: i64) -> i64 {
    std::panic::catch_unwind(|| {
        // Validate pointers
        if path.is_null() || key.is_null() {
            return ERR_INVALID_PATH as i64;
        }

        // Convert C strings to Rust strings
        let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
            Ok(s) => s,
            Err(_) => return ERR_INVALID_PATH as i64,
        };

        let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
            Ok(s) => s,
            Err(_) => return ERR_INVALID_PATH as i64,
        };

        // Call with_db to append the value
        match with_db(path_str, |db| db.append(key_str, Atom::Int(value))) {
            Ok(offset) => offset as i64,
            Err(crate::error::SynaError::NotFound(_)) => crate::error::ERR_DB_NOT_FOUND as i64,
            Err(_) => ERR_GENERIC as i64,
        }
    })
    .unwrap_or(ERR_INTERNAL_PANIC as i64)
}

/// Writes a text value to the database.
///
/// # Arguments
/// * `path` - Null-terminated C string containing the path to the database file
/// * `key` - Null-terminated C string containing the key
/// * `value` - Null-terminated C string containing the text value to store
///
/// # Returns
/// * Positive value - The byte offset where the entry was written
/// * `-1` (ERR_DB_NOT_FOUND) - Database not found in registry
/// * `-2` (ERR_INVALID_PATH) - Path, key, or value is null or invalid UTF-8
/// * `-100` (ERR_INTERNAL_PANIC) - Internal panic occurred
///
/// # Safety
/// * `path`, `key`, and `value` must be valid null-terminated C strings or null
///
/// _Requirements: 6.2, 6.3_
#[no_mangle]
pub extern "C" fn SYNA_put_text(
    path: *const c_char,
    key: *const c_char,
    value: *const c_char,
) -> i64 {
    std::panic::catch_unwind(|| {
        // Validate pointers
        if path.is_null() || key.is_null() || value.is_null() {
            return ERR_INVALID_PATH as i64;
        }

        // Convert C strings to Rust strings
        let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
            Ok(s) => s,
            Err(_) => return ERR_INVALID_PATH as i64,
        };

        let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
            Ok(s) => s,
            Err(_) => return ERR_INVALID_PATH as i64,
        };

        let value_str = match unsafe { CStr::from_ptr(value) }.to_str() {
            Ok(s) => s,
            Err(_) => return ERR_INVALID_PATH as i64,
        };

        // Call with_db to append the value
        match with_db(path_str, |db| {
            db.append(key_str, Atom::Text(value_str.to_string()))
        }) {
            Ok(offset) => offset as i64,
            Err(crate::error::SynaError::NotFound(_)) => crate::error::ERR_DB_NOT_FOUND as i64,
            Err(_) => ERR_GENERIC as i64,
        }
    })
    .unwrap_or(ERR_INTERNAL_PANIC as i64)
}

/// Writes a byte array to the database.
///
/// # Arguments
/// * `path` - Null-terminated C string containing the path to the database file
/// * `key` - Null-terminated C string containing the key
/// * `data` - Pointer to the byte array to store
/// * `len` - Length of the byte array
///
/// # Returns
/// * Positive value - The byte offset where the entry was written
/// * `-1` (ERR_DB_NOT_FOUND) - Database not found in registry
/// * `-2` (ERR_INVALID_PATH) - Path, key, or data is null or invalid UTF-8
/// * `-100` (ERR_INTERNAL_PANIC) - Internal panic occurred
///
/// # Safety
/// * `path` and `key` must be valid null-terminated C strings or null
/// * `data` must be a valid pointer to at least `len` bytes, or null
///
/// _Requirements: 6.2, 6.4_
#[no_mangle]
pub extern "C" fn SYNA_put_bytes(
    path: *const c_char,
    key: *const c_char,
    data: *const u8,
    len: usize,
) -> i64 {
    std::panic::catch_unwind(|| {
        // Validate pointers
        if path.is_null() || key.is_null() || (data.is_null() && len > 0) {
            return ERR_INVALID_PATH as i64;
        }

        // Convert C strings to Rust strings
        let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
            Ok(s) => s,
            Err(_) => return ERR_INVALID_PATH as i64,
        };

        let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
            Ok(s) => s,
            Err(_) => return ERR_INVALID_PATH as i64,
        };

        // Create Vec<u8> from raw pointer and length
        let bytes = if len == 0 {
            Vec::new()
        } else {
            unsafe { std::slice::from_raw_parts(data, len) }.to_vec()
        };

        // Call with_db to append the value
        match with_db(path_str, |db| db.append(key_str, Atom::Bytes(bytes))) {
            Ok(offset) => offset as i64,
            Err(crate::error::SynaError::NotFound(_)) => crate::error::ERR_DB_NOT_FOUND as i64,
            Err(_) => ERR_GENERIC as i64,
        }
    })
    .unwrap_or(ERR_INTERNAL_PANIC as i64)
}

/// Reads a float value from the database.
///
/// # Arguments
/// * `path` - Null-terminated C string containing the path to the database file
/// * `key` - Null-terminated C string containing the key
/// * `out` - Pointer to write the f64 value to
///
/// # Returns
/// * `1` (ERR_SUCCESS) - Value read successfully
/// * `-1` (ERR_DB_NOT_FOUND) - Database not found in registry
/// * `-2` (ERR_INVALID_PATH) - Path, key, or out is null or invalid UTF-8
/// * `-5` (ERR_KEY_NOT_FOUND) - Key not found in database
/// * `-6` (ERR_TYPE_MISMATCH) - Value exists but is not a Float
/// * `-100` (ERR_INTERNAL_PANIC) - Internal panic occurred
///
/// # Safety
/// * `path` and `key` must be valid null-terminated C strings or null
/// * `out` must be a valid pointer to an f64
///
/// _Requirements: 6.2, 6.4_
#[no_mangle]
pub extern "C" fn SYNA_get_float(path: *const c_char, key: *const c_char, out: *mut f64) -> i32 {
    std::panic::catch_unwind(|| {
        // Validate pointers
        if path.is_null() || key.is_null() || out.is_null() {
            return ERR_INVALID_PATH;
        }

        // Convert C strings to Rust strings
        let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
            Ok(s) => s,
            Err(_) => return ERR_INVALID_PATH,
        };

        let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
            Ok(s) => s,
            Err(_) => return ERR_INVALID_PATH,
        };

        // Call with_db to get the value
        match with_db(path_str, |db| db.get(key_str)) {
            Ok(Some(Atom::Float(f))) => {
                // Write value to out pointer
                unsafe { *out = f };
                ERR_SUCCESS
            }
            Ok(Some(_)) => {
                // Value exists but is not a Float
                ERR_TYPE_MISMATCH
            }
            Ok(None) => {
                // Key not found
                ERR_KEY_NOT_FOUND
            }
            Err(crate::error::SynaError::NotFound(_)) => crate::error::ERR_DB_NOT_FOUND,
            Err(_) => ERR_GENERIC,
        }
    })
    .unwrap_or(ERR_INTERNAL_PANIC)
}

/// Reads an integer value from the database.
///
/// # Arguments
/// * `path` - Null-terminated C string containing the path to the database file
/// * `key` - Null-terminated C string containing the key
/// * `out` - Pointer to write the i64 value to
///
/// # Returns
/// * `1` (ERR_SUCCESS) - Value read successfully
/// * `-1` (ERR_DB_NOT_FOUND) - Database not found in registry
/// * `-2` (ERR_INVALID_PATH) - Path, key, or out is null or invalid UTF-8
/// * `-5` (ERR_KEY_NOT_FOUND) - Key not found in database
/// * `-6` (ERR_TYPE_MISMATCH) - Value exists but is not an Int
/// * `-100` (ERR_INTERNAL_PANIC) - Internal panic occurred
///
/// # Safety
/// * `path` and `key` must be valid null-terminated C strings or null
/// * `out` must be a valid pointer to an i64
///
/// _Requirements: 6.2, 6.4_
#[no_mangle]
pub extern "C" fn SYNA_get_int(path: *const c_char, key: *const c_char, out: *mut i64) -> i32 {
    std::panic::catch_unwind(|| {
        // Validate pointers
        if path.is_null() || key.is_null() || out.is_null() {
            return ERR_INVALID_PATH;
        }

        // Convert C strings to Rust strings
        let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
            Ok(s) => s,
            Err(_) => return ERR_INVALID_PATH,
        };

        let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
            Ok(s) => s,
            Err(_) => return ERR_INVALID_PATH,
        };

        // Call with_db to get the value
        match with_db(path_str, |db| db.get(key_str)) {
            Ok(Some(Atom::Int(i))) => {
                // Write value to out pointer
                unsafe { *out = i };
                ERR_SUCCESS
            }
            Ok(Some(_)) => {
                // Value exists but is not an Int
                ERR_TYPE_MISMATCH
            }
            Ok(None) => {
                // Key not found
                ERR_KEY_NOT_FOUND
            }
            Err(crate::error::SynaError::NotFound(_)) => crate::error::ERR_DB_NOT_FOUND,
            Err(_) => ERR_GENERIC,
        }
    })
    .unwrap_or(ERR_INTERNAL_PANIC)
}

/// Retrieves the complete history of float values for a key as a contiguous array.
///
/// This function is designed for AI/ML workloads where you need to feed time-series
/// data directly to frameworks like PyTorch or TensorFlow.
///
/// # Arguments
/// * `path` - Null-terminated C string containing the path to the database file
/// * `key` - Null-terminated C string containing the key
/// * `out_len` - Pointer to write the array length to
///
/// # Returns
/// * Non-null pointer to contiguous f64 array on success
/// * Null pointer on error (check out_len for error code)
///
/// # Error Codes (written to out_len on error)
/// * `0` - Empty history (no float values for this key)
///
/// # Safety
/// * `path` and `key` must be valid null-terminated C strings or null
/// * `out_len` must be a valid pointer to a usize
/// * The returned pointer MUST be freed using `SYNA_free_tensor()` to avoid memory leaks
///
/// _Requirements: 4.2, 6.4_
#[no_mangle]
pub extern "C" fn SYNA_get_history_tensor(
    path: *const c_char,
    key: *const c_char,
    out_len: *mut usize,
) -> *mut f64 {
    std::panic::catch_unwind(|| {
        // Validate pointers
        if path.is_null() || key.is_null() || out_len.is_null() {
            return std::ptr::null_mut();
        }

        // Convert C strings to Rust strings
        let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
            Ok(s) => s,
            Err(_) => return std::ptr::null_mut(),
        };

        let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
            Ok(s) => s,
            Err(_) => return std::ptr::null_mut(),
        };

        // Call with_db to get the history tensor
        match with_db(path_str, |db| db.get_history_tensor(key_str)) {
            Ok((ptr, len)) => {
                // Write length to out_len pointer
                unsafe { *out_len = len };
                ptr
            }
            Err(_) => {
                // Set out_len to 0 on error
                unsafe { *out_len = 0 };
                std::ptr::null_mut()
            }
        }
    })
    .unwrap_or(std::ptr::null_mut())
}

/// Frees memory allocated by `SYNA_get_history_tensor()`.
///
/// # Arguments
/// * `ptr` - Pointer returned by `SYNA_get_history_tensor()`
/// * `len` - Length returned by `SYNA_get_history_tensor()`
///
/// # Safety
/// * `ptr` must have been returned by `SYNA_get_history_tensor()`
/// * `len` must be the length returned alongside the pointer
/// * This function must only be called once per pointer
/// * Calling with a null pointer or zero length is safe (no-op)
///
/// _Requirements: 4.3, 6.5_
#[no_mangle]
pub extern "C" fn SYNA_free_tensor(ptr: *mut f64, len: usize) {
    std::panic::catch_unwind(|| {
        // Call internal free_tensor (safe wrapper)
        unsafe { free_tensor(ptr, len) };
    })
    .ok(); // Ignore panic result - we don't want to propagate panics from free
}

/// Deletes a key from the database by appending a tombstone entry.
///
/// # Arguments
/// * `path` - Null-terminated C string containing the path to the database file
/// * `key` - Null-terminated C string containing the key to delete
///
/// # Returns
/// * `1` (ERR_SUCCESS) - Key deleted successfully
/// * `0` (ERR_GENERIC) - Generic error during delete
/// * `-1` (ERR_DB_NOT_FOUND) - Database not found in registry
/// * `-2` (ERR_INVALID_PATH) - Path or key is null or invalid UTF-8
/// * `-100` (ERR_INTERNAL_PANIC) - Internal panic occurred
///
/// # Safety
/// * `path` and `key` must be valid null-terminated C strings or null
///
/// _Requirements: 10.1_
#[no_mangle]
pub extern "C" fn SYNA_delete(path: *const c_char, key: *const c_char) -> i32 {
    std::panic::catch_unwind(|| {
        // Validate pointers
        if path.is_null() || key.is_null() {
            return ERR_INVALID_PATH;
        }

        // Convert C strings to Rust strings
        let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
            Ok(s) => s,
            Err(_) => return ERR_INVALID_PATH,
        };

        let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
            Ok(s) => s,
            Err(_) => return ERR_INVALID_PATH,
        };

        // Call with_db to delete the key
        match with_db(path_str, |db| db.delete(key_str)) {
            Ok(_) => ERR_SUCCESS,
            Err(crate::error::SynaError::NotFound(_)) => crate::error::ERR_DB_NOT_FOUND,
            Err(_) => ERR_GENERIC,
        }
    })
    .unwrap_or(ERR_INTERNAL_PANIC)
}

/// Checks if a key exists in the database and is not deleted.
///
/// # Arguments
/// * `path` - Null-terminated C string containing the path to the database file
/// * `key` - Null-terminated C string containing the key to check
///
/// # Returns
/// * `1` - Key exists and is not deleted
/// * `0` - Key does not exist or is deleted
/// * `-1` (ERR_DB_NOT_FOUND) - Database not found in registry
/// * `-2` (ERR_INVALID_PATH) - Path or key is null or invalid UTF-8
/// * `-100` (ERR_INTERNAL_PANIC) - Internal panic occurred
///
/// # Safety
/// * `path` and `key` must be valid null-terminated C strings or null
///
/// _Requirements: 10.2_
#[no_mangle]
pub extern "C" fn SYNA_exists(path: *const c_char, key: *const c_char) -> i32 {
    std::panic::catch_unwind(|| {
        // Validate pointers
        if path.is_null() || key.is_null() {
            return ERR_INVALID_PATH;
        }

        // Convert C strings to Rust strings
        let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
            Ok(s) => s,
            Err(_) => return ERR_INVALID_PATH,
        };

        let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
            Ok(s) => s,
            Err(_) => return ERR_INVALID_PATH,
        };

        // Call with_db to check if key exists
        match with_db(path_str, |db| Ok(db.exists(key_str))) {
            Ok(true) => 1,
            Ok(false) => 0,
            Err(crate::error::SynaError::NotFound(_)) => crate::error::ERR_DB_NOT_FOUND,
            Err(_) => ERR_GENERIC,
        }
    })
    .unwrap_or(ERR_INTERNAL_PANIC)
}

/// Compacts the database by rewriting only the latest non-deleted entries.
///
/// This operation reclaims disk space by removing deleted entries and old versions.
/// After compaction, `get_history()` will only return the latest value for each key.
///
/// # Arguments
/// * `path` - Null-terminated C string containing the path to the database file
///
/// # Returns
/// * `1` (ERR_SUCCESS) - Compaction completed successfully
/// * `0` (ERR_GENERIC) - Generic error during compaction
/// * `-1` (ERR_DB_NOT_FOUND) - Database not found in registry
/// * `-2` (ERR_INVALID_PATH) - Path is null or invalid UTF-8
/// * `-100` (ERR_INTERNAL_PANIC) - Internal panic occurred
///
/// # Safety
/// * `path` must be a valid null-terminated C string or null
///
/// _Requirements: 11.1_
#[no_mangle]
pub extern "C" fn SYNA_compact(path: *const c_char) -> i32 {
    std::panic::catch_unwind(|| {
        // Validate pointer
        if path.is_null() {
            return ERR_INVALID_PATH;
        }

        // Convert C string to Rust string
        let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
            Ok(s) => s,
            Err(_) => return ERR_INVALID_PATH,
        };

        // Call with_db to compact the database
        match with_db(path_str, |db| db.compact()) {
            Ok(_) => ERR_SUCCESS,
            Err(crate::error::SynaError::NotFound(_)) => crate::error::ERR_DB_NOT_FOUND,
            Err(_) => ERR_GENERIC,
        }
    })
    .unwrap_or(ERR_INTERNAL_PANIC)
}

/// Returns a list of all non-deleted keys in the database.
///
/// # Arguments
/// * `path` - Null-terminated C string containing the path to the database file
/// * `out_len` - Pointer to write the number of keys to
///
/// # Returns
/// * Non-null pointer to array of null-terminated C strings on success
/// * Null pointer on error
///
/// # Safety
/// * `path` must be a valid null-terminated C string or null
/// * `out_len` must be a valid pointer to a usize
/// * The returned pointer MUST be freed using `SYNA_free_keys()` to avoid memory leaks
///
/// _Requirements: 10.5_
#[no_mangle]
pub extern "C" fn SYNA_keys(path: *const c_char, out_len: *mut usize) -> *mut *mut c_char {
    std::panic::catch_unwind(|| {
        // Validate pointers
        if path.is_null() || out_len.is_null() {
            return std::ptr::null_mut();
        }

        // Convert C string to Rust string
        let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
            Ok(s) => s,
            Err(_) => return std::ptr::null_mut(),
        };

        // Call with_db to get keys
        match with_db(path_str, |db| Ok(db.keys())) {
            Ok(keys) => {
                let len = keys.len();

                if len == 0 {
                    // No keys - return null with length 0
                    unsafe { *out_len = 0 };
                    return std::ptr::null_mut();
                }

                // Allocate array of C string pointers
                let mut c_strings: Vec<*mut c_char> = Vec::with_capacity(len);

                for key in keys {
                    // Convert each key to a C string
                    // Add null terminator
                    let mut bytes = key.into_bytes();
                    bytes.push(0); // null terminator

                    // Allocate memory for the C string
                    let c_str = bytes.into_boxed_slice();
                    let ptr = Box::into_raw(c_str) as *mut c_char;
                    c_strings.push(ptr);
                }

                // Write length to out_len
                unsafe { *out_len = len };

                // Convert Vec to boxed slice and leak for FFI
                let boxed = c_strings.into_boxed_slice();
                Box::into_raw(boxed) as *mut *mut c_char
            }
            Err(_) => {
                unsafe { *out_len = 0 };
                std::ptr::null_mut()
            }
        }
    })
    .unwrap_or(std::ptr::null_mut())
}

/// Frees memory allocated by `SYNA_keys()`.
///
/// # Arguments
/// * `keys` - Pointer returned by `SYNA_keys()`
/// * `len` - Length returned by `SYNA_keys()`
///
/// # Safety
/// * `keys` must have been returned by `SYNA_keys()`
/// * `len` must be the length returned alongside the pointer
/// * This function must only be called once per pointer
/// * Calling with a null pointer or zero length is safe (no-op)
///
/// _Requirements: 6.5_
#[no_mangle]
pub extern "C" fn SYNA_free_keys(keys: *mut *mut c_char, len: usize) {
    std::panic::catch_unwind(|| {
        if keys.is_null() || len == 0 {
            return;
        }

        unsafe {
            // Reconstruct the slice of pointers
            let key_slice = std::slice::from_raw_parts_mut(keys, len);

            // Free each individual string
            for key_ptr in key_slice.iter() {
                if !key_ptr.is_null() {
                    // Find the length of the C string (including null terminator)
                    let c_str = CStr::from_ptr(*key_ptr);
                    let str_len = c_str.to_bytes_with_nul().len();

                    // Reconstruct the box and drop it
                    let _ =
                        Box::from_raw(std::slice::from_raw_parts_mut(*key_ptr as *mut u8, str_len));
                }
            }

            // Free the array itself
            let _ = Box::from_raw(std::slice::from_raw_parts_mut(keys, len));
        }
    })
    .ok(); // Ignore panic result - we don't want to propagate panics from free
}

/// Reads a text value from the database.
///
/// # Arguments
/// * `path` - Null-terminated C string containing the path to the database file
/// * `key` - Null-terminated C string containing the key
/// * `out_len` - Pointer to write the string length to (excluding null terminator)
///
/// # Returns
/// * Non-null pointer to null-terminated C string on success
/// * Null pointer on error
///
/// # Error Codes (check return value)
/// * Null with out_len = 0 - Key not found or type mismatch
///
/// # Safety
/// * `path` and `key` must be valid null-terminated C strings or null
/// * `out_len` must be a valid pointer to a usize
/// * The returned pointer MUST be freed using `SYNA_free_text()` to avoid memory leaks
///
/// _Requirements: 6.2, 6.4_
#[no_mangle]
pub extern "C" fn SYNA_get_text(
    path: *const c_char,
    key: *const c_char,
    out_len: *mut usize,
) -> *mut c_char {
    std::panic::catch_unwind(|| {
        // Validate pointers
        if path.is_null() || key.is_null() || out_len.is_null() {
            return std::ptr::null_mut();
        }

        // Convert C strings to Rust strings
        let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
            Ok(s) => s,
            Err(_) => return std::ptr::null_mut(),
        };

        let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
            Ok(s) => s,
            Err(_) => return std::ptr::null_mut(),
        };

        // Call with_db to get the value
        match with_db(path_str, |db| db.get(key_str)) {
            Ok(Some(Atom::Text(s))) => {
                let len = s.len();
                unsafe { *out_len = len };

                // Convert to C string with null terminator
                let mut bytes = s.into_bytes();
                bytes.push(0); // null terminator

                let c_str = bytes.into_boxed_slice();
                Box::into_raw(c_str) as *mut c_char
            }
            Ok(Some(_)) => {
                // Value exists but is not Text
                unsafe { *out_len = 0 };
                std::ptr::null_mut()
            }
            Ok(None) => {
                // Key not found
                unsafe { *out_len = 0 };
                std::ptr::null_mut()
            }
            Err(_) => {
                unsafe { *out_len = 0 };
                std::ptr::null_mut()
            }
        }
    })
    .unwrap_or(std::ptr::null_mut())
}

/// Frees memory allocated by `SYNA_get_text()`.
///
/// # Arguments
/// * `ptr` - Pointer returned by `SYNA_get_text()`
/// * `len` - Length returned by `SYNA_get_text()` (excluding null terminator)
///
/// # Safety
/// * `ptr` must have been returned by `SYNA_get_text()`
/// * `len` must be the length returned alongside the pointer
/// * This function must only be called once per pointer
/// * Calling with a null pointer is safe (no-op)
///
/// _Requirements: 6.5_
#[no_mangle]
pub extern "C" fn SYNA_free_text(ptr: *mut c_char, len: usize) {
    std::panic::catch_unwind(|| {
        if ptr.is_null() {
            return;
        }

        unsafe {
            // Reconstruct the box (len + 1 for null terminator)
            let _ = Box::from_raw(std::slice::from_raw_parts_mut(ptr as *mut u8, len + 1));
        }
    })
    .ok();
}

/// Reads a byte array from the database.
///
/// # Arguments
/// * `path` - Null-terminated C string containing the path to the database file
/// * `key` - Null-terminated C string containing the key
/// * `out_len` - Pointer to write the array length to
///
/// # Returns
/// * Non-null pointer to byte array on success
/// * Null pointer on error
///
/// # Error Codes (check return value)
/// * Null with out_len = 0 - Key not found or type mismatch
///
/// # Safety
/// * `path` and `key` must be valid null-terminated C strings or null
/// * `out_len` must be a valid pointer to a usize
/// * The returned pointer MUST be freed using `SYNA_free_bytes()` to avoid memory leaks
///
/// _Requirements: 6.2, 6.4_
#[no_mangle]
pub extern "C" fn SYNA_get_bytes(
    path: *const c_char,
    key: *const c_char,
    out_len: *mut usize,
) -> *mut u8 {
    std::panic::catch_unwind(|| {
        // Validate pointers
        if path.is_null() || key.is_null() || out_len.is_null() {
            return std::ptr::null_mut();
        }

        // Convert C strings to Rust strings
        let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
            Ok(s) => s,
            Err(_) => return std::ptr::null_mut(),
        };

        let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
            Ok(s) => s,
            Err(_) => return std::ptr::null_mut(),
        };

        // Call with_db to get the value
        match with_db(path_str, |db| db.get(key_str)) {
            Ok(Some(Atom::Bytes(bytes))) => {
                let len = bytes.len();

                if len == 0 {
                    unsafe { *out_len = 0 };
                    return std::ptr::null_mut();
                }

                unsafe { *out_len = len };

                // Convert to boxed slice and leak for FFI
                let boxed = bytes.into_boxed_slice();
                Box::into_raw(boxed) as *mut u8
            }
            Ok(Some(_)) => {
                // Value exists but is not Bytes
                unsafe { *out_len = 0 };
                std::ptr::null_mut()
            }
            Ok(None) => {
                // Key not found
                unsafe { *out_len = 0 };
                std::ptr::null_mut()
            }
            Err(_) => {
                unsafe { *out_len = 0 };
                std::ptr::null_mut()
            }
        }
    })
    .unwrap_or(std::ptr::null_mut())
}

/// Frees memory allocated by `SYNA_get_bytes()`.
///
/// # Arguments
/// * `ptr` - Pointer returned by `SYNA_get_bytes()`
/// * `len` - Length returned by `SYNA_get_bytes()`
///
/// # Safety
/// * `ptr` must have been returned by `SYNA_get_bytes()`
/// * `len` must be the length returned alongside the pointer
/// * This function must only be called once per pointer
/// * Calling with a null pointer or zero length is safe (no-op)
///
/// _Requirements: 6.5_
#[no_mangle]
pub extern "C" fn SYNA_free_bytes(ptr: *mut u8, len: usize) {
    std::panic::catch_unwind(|| {
        if ptr.is_null() || len == 0 {
            return;
        }

        unsafe {
            let _ = Box::from_raw(std::slice::from_raw_parts_mut(ptr, len));
        }
    })
    .ok();
}
