thread_local! {
    static LAST_ERROR_CODE: RefCell<i32> = const { RefCell::new(0) };
    static LAST_ERROR_CONTEXT: RefCell<Option<CString>> = const { RefCell::new(None) };
    static LAST_RETURN_LEN: RefCell<usize> = const { RefCell::new(0) };
}

fn set_last_error(code: i32, message: &str) {
    LAST_ERROR_CODE.with_borrow_mut(|c| *c = code);
    LAST_ERROR_CONTEXT.with_borrow_mut(|c| *c = CString::new(message).ok());
    LAST_RETURN_LEN.with_borrow_mut(|c| *c = 0);
}

fn clear_last_error() {
    LAST_ERROR_CODE.with_borrow_mut(|c| *c = 0);
    LAST_ERROR_CONTEXT.with_borrow_mut(|c| *c = None);
    LAST_RETURN_LEN.with_borrow_mut(|c| *c = 0);
}

fn set_last_return_len(len: usize) {
    LAST_RETURN_LEN.with_borrow_mut(|c| *c = len);
}

fn last_return_len() -> usize {
    LAST_RETURN_LEN.with_borrow(|c| *c)
}

/// Return the last error code (0 means no error).
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// This function does not allocate and returns no owned pointer.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn {{ prefix }}_last_error_code() -> i32 {
    LAST_ERROR_CODE.with_borrow(|c| *c)
}

/// Return the last error message. The pointer is borrowed and valid until the next FFI call on this thread.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// The returned pointer is borrowed from thread-local storage and must NOT be freed.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn {{ prefix }}_last_error_context() -> *const c_char {
    LAST_ERROR_CONTEXT.with_borrow(|ctx| {
        ctx.as_ref().map_or(std::ptr::null(), |c| c.as_ptr())
    })
}
