//! FFI 边界类型定义
//!
//! 主程序与动态插件 (.so) 之间的 C ABI 契约。
//! 所有跨边界数据通过 JSON 字符串传递，避免 Rust ABI 不稳定问题。

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

/// 插件实例的不透明句柄
pub type PluginHandle = *mut c_void;

/// FFI 安全的字符串：指向 C 字符串 + 长度。
///
/// 该类型本身不携带 allocator 元数据，以保持现有 `repr(C)` ABI 稳定；
/// 调用方必须通过 API 约定追踪所有权，并使用分配该字符串的一侧提供的释放函数。
///
/// # Safety
/// - 宿主返回给宿主的 `FfiString` 只能由宿主分配器释放。
/// - 动态插件返回给宿主的 `FfiString` 只能通过对应 `PluginVTable::free_string`
///   释放，不能调用宿主侧释放逻辑。
/// - 跨 allocator 释放会导致未定义行为（可能崩溃或内存损坏）。
#[repr(C)]
pub struct FfiString {
    pub ptr: *mut c_char,
    pub len: usize,
}

impl FfiString {
    /// 从 Rust String 创建 `FfiString`（转移所有权到调用方一侧）
    ///
    /// 生成的指针必须回到同一 allocator 释放。
    pub fn from_string(s: String) -> Self {
        match CString::new(s) {
            Ok(cstr) => {
                let len = cstr.as_bytes().len();
                Self {
                    ptr: cstr.into_raw(),
                    len,
                }
            }
            Err(_) => Self::null(),
        }
    }

    /// 空字符串
    pub fn null() -> Self {
        Self {
            ptr: ptr::null_mut(),
            len: 0,
        }
    }

    /// 复制为 Rust String（不释放底层内存）
    ///
    /// # Safety
    /// `ptr` 必须是由当前 allocator 的 `CString::into_raw` 产生的有效指针。
    /// 调用此函数不会改变所有权，原始分配方仍负责释放该内存。
    pub unsafe fn to_string(&self) -> Option<String> {
        if self.ptr.is_null() {
            return None;
        }
        unsafe { CStr::from_ptr(self.ptr) }
            .to_str()
            .ok()
            .map(str::to_owned)
    }
}

/// 只读 FFI 字符串切片（借用，不转移所有权）
pub type FfiStr = *const c_char;

/// FFI 调用结果
#[repr(C)]
pub struct FfiResult {
    /// 0 = 成功, 非零 = 失败
    pub code: c_int,
    /// 错误信息（成功时为 null）
    pub error: FfiString,
}

impl FfiResult {
    pub fn ok() -> Self {
        Self {
            code: 0,
            error: FfiString::null(),
        }
    }

    pub fn err(msg: String) -> Self {
        Self {
            code: -1,
            error: FfiString::from_string(msg),
        }
    }

    /// 转换为 Rust Result（不释放 error 底层内存）
    ///
    /// # Safety
    /// 如果 error 非 null，必须是由 CString::into_raw 产生的有效指针
    pub unsafe fn to_result(&self) -> Result<(), String> {
        if self.code == 0 {
            Ok(())
        } else {
            let msg = unsafe { self.error.to_string() }
                .unwrap_or_else(|| "unknown plugin error".to_string());
            Err(msg)
        }
    }
}

/// 插件向主程序发消息的回调函数类型
/// envelope_json: JSON 序列化的 Envelope
///
/// # Safety
/// 插件若把该回调保存到后台线程，必须保证在线程完全退出后再让 `stop()` 返回；
/// 一旦宿主开始卸载插件，回调会先被熔断为 no-op，随后执行 `stop()`/`destroy()`。
pub type SendCallback = unsafe extern "C" fn(ctx: *mut c_void, envelope_json: FfiStr);

/// 插件虚函数表 — 每个动态插件导出一个此结构体
#[repr(C)]
pub struct PluginVTable {
    /// 创建插件实例，返回不透明句柄
    pub create: unsafe extern "C" fn() -> PluginHandle,

    /// 获取插件信息（返回 JSON 序列化的 PluginInfo）
    pub get_info: unsafe extern "C" fn(handle: PluginHandle) -> FfiString,

    /// 初始化插件
    /// config_json: 完整的 AppConfig JSON
    /// send_cb: 发送消息的回调
    pub init: unsafe extern "C" fn(
        handle: PluginHandle,
        config_json: FfiStr,
        send_ctx: *mut c_void,
        send_cb: SendCallback,
    ) -> FfiResult,

    /// 启动插件
    pub start: unsafe extern "C" fn(handle: PluginHandle) -> FfiResult,

    /// 处理消息
    /// message_json: JSON 序列化的 Message
    pub handle_message:
        unsafe extern "C" fn(handle: PluginHandle, message_json: FfiStr) -> FfiResult,

    /// 停止插件
    pub stop: unsafe extern "C" fn(handle: PluginHandle) -> FfiResult,

    /// 释放插件分配的 `FfiString`
    ///
    /// # Safety
    /// 只能用于释放该插件自己返回的字符串。宿主分配的 `FfiString` 绝不能传给这里，
    /// 否则会发生跨 allocator 释放。
    pub free_string: unsafe extern "C" fn(s: FfiString),

    /// 销毁插件实例，释放资源
    pub destroy: unsafe extern "C" fn(handle: PluginHandle),

    /// 获取功能列表 (返回 JSON: Vec<String>)
    pub get_capabilities: unsafe extern "C" fn(handle: PluginHandle) -> FfiString,

    /// 运行自测 (返回 JSON: Vec<CapabilityTestResult>)
    pub self_test: unsafe extern "C" fn(handle: PluginHandle) -> FfiString,
}

/// 动态插件 .so 中导出的符号名称
pub const PLUGIN_VTABLE_SYMBOL: &[u8] = b"showen_plugin_vtable\0";

/// 从 FfiStr 安全读取为 &str
///
/// # Safety
/// ptr 必须指向有效的 null-terminated C 字符串
pub unsafe fn ffi_str_to_str<'a>(ptr: FfiStr) -> Option<&'a str> {
    if ptr.is_null() {
        return None;
    }
    unsafe { CStr::from_ptr(ptr) }.to_str().ok()
}
