//! PDO 变化检测监控器
//!
//! 提供对从站 PDO 输入数据的变化监控功能，
//! 支持按字节偏移范围监视，每次 `check()` 调用自动更新快照并报告变化。

use std::collections::HashMap;
use crate::data::error::{DarraError, Result};
use crate::utils::ffi;
use std::os::raw::c_int;
use std::ptr::NonNull;

// ===================== 监视配置 =====================

/// 单个监视点配置
#[derive(Debug, Clone)]
pub struct WatchPoint {
    /// 监视点名称（用于识别变化来源）
    pub name: String,
    /// 在从站输入 IOmap 中的字节偏移（含）
    pub byte_offset: u32,
    /// 监视字节数
    pub byte_count: u32,
    /// 上一次的数据快照
    snapshot: Vec<u8>,
}

impl WatchPoint {
    /// 创建新监视点（初始快照为全零）
    fn new(name: impl Into<String>, byte_offset: u32, byte_count: u32) -> Self {
        Self {
            name: name.into(),
            byte_offset,
            byte_count,
            snapshot: vec![0u8; byte_count as usize],
        }
    }
}

// ===================== 变化报告 =====================

/// PDO 变化报告（每次 `check()` 调用后产生）
#[derive(Debug, Clone)]
pub struct PdoChange {
    /// 监视点名称
    pub watch_name: String,
    /// 从站索引
    pub slave_index: u16,
    /// 旧数据快照
    pub old_data: Vec<u8>,
    /// 新数据
    pub new_data: Vec<u8>,
}

impl PdoChange {
    /// 按字节数组格式格式化变化信息
    pub fn format_hex(&self) -> String {
        let old_hex: String = self.old_data.iter().map(|b| format!("{:02X}", b)).collect::<Vec<_>>().join(" ");
        let new_hex: String = self.new_data.iter().map(|b| format!("{:02X}", b)).collect::<Vec<_>>().join(" ");
        format!("[{}] 从站 {}: {} -> {}", self.watch_name, self.slave_index, old_hex, new_hex)
    }
}

// ===================== PDO 监控器 =====================

/// PDO 输入变化监控器
///
/// 通过轮询 IOmap 输入指针，检测指定字节范围是否发生变化。
/// 适合在 PDO 回调线程或独立轮询线程中周期性调用 `check()`。
pub struct PdoMonitor {
    /// 主站索引
    master_index: u16,
    /// 监视点集合：key = (从站索引, 监视点名称)
    watches: HashMap<(u16, String), WatchPoint>,
    /// 总变化计数（自创建以来）
    pub total_changes: u64,
}

impl PdoMonitor {
    /// 创建新的 PDO 监控器
    pub fn new(master_index: u16) -> Self {
        Self {
            master_index,
            watches: HashMap::new(),
            total_changes: 0,
        }
    }

    /// 添加监视点
    ///
    /// - `slave_index`: 目标从站
    /// - `name`: 监视点名称（同一从站内唯一）
    /// - `byte_offset`: 在从站输入 IOmap 中的字节偏移
    /// - `byte_count`: 监视字节数（建议不超过实际输入大小）
    ///
    /// 若同名监视点已存在则覆盖。
    pub fn watch(
        &mut self,
        slave_index: u16,
        name: impl Into<String>,
        byte_offset: u32,
        byte_count: u32,
    ) {
        let name = name.into();
        // 立即读取当前值作为初始快照，避免首次 check 时产生误报
        let initial = self.read_input_bytes(slave_index, byte_offset, byte_count)
            .unwrap_or_else(|_| vec![0u8; byte_count as usize]);
        let mut wp = WatchPoint::new(name.clone(), byte_offset, byte_count);
        wp.snapshot = initial;
        self.watches.insert((slave_index, name), wp);
    }

    /// 移除监视点
    pub fn unwatch(&mut self, slave_index: u16, name: &str) {
        self.watches.remove(&(slave_index, name.to_string()));
    }

    /// 移除指定从站的所有监视点
    pub fn unwatch_slave(&mut self, slave_index: u16) {
        self.watches.retain(|(s, _), _| *s != slave_index);
    }

    /// 执行一次变化检测
    ///
    /// 读取所有监视点的当前数据，与快照比较。
    /// 若发生变化，更新快照并将变化信息加入返回列表。
    pub fn check(&mut self) -> Vec<PdoChange> {
        let mut changes = Vec::new();
        // 先收集需要更新的键和数据，避免借用冲突
        let keys: Vec<(u16, String)> = self.watches.keys().cloned().collect();

        for key in keys {
            // 先克隆出读取参数, 释放 &self.watches 借用
            let (byte_offset, byte_count) = match self.watches.get(&key) {
                Some(v) => (v.byte_offset, v.byte_count),
                None => continue,
            };
            let slave_index = key.0;
            let current = match self.read_input_bytes(slave_index, byte_offset, byte_count) {
                Ok(v) => v,
                Err(_) => continue,
            };
            // 再可变借用更新
            let wp = match self.watches.get_mut(&key) {
                Some(v) => v,
                None => continue,
            };
            if current != wp.snapshot {
                let change = PdoChange {
                    watch_name: wp.name.clone(),
                    slave_index,
                    old_data: wp.snapshot.clone(),
                    new_data: current.clone(),
                };
                wp.snapshot = current;
                self.total_changes += 1;
                changes.push(change);
            }
        }
        changes
    }

    /// 检查并仅返回指定从站的变化
    pub fn check_slave(&mut self, slave_index: u16) -> Vec<PdoChange> {
        self.check()
            .into_iter()
            .filter(|c| c.slave_index == slave_index)
            .collect()
    }

    /// 重置所有监视点的快照为当前实际值（清除历史基准）
    pub fn reset_snapshots(&mut self) {
        let keys: Vec<(u16, String)> = self.watches.keys().cloned().collect();
        for key in keys {
            let slave_index = key.0;
            let (byte_offset, byte_count) = match self.watches.get(&key) {
                Some(v) => (v.byte_offset, v.byte_count),
                None => continue,
            };
            if let Ok(data) = self.read_input_bytes(slave_index, byte_offset, byte_count) {
                if let Some(wp) = self.watches.get_mut(&key) {
                    wp.snapshot = data;
                }
            }
        }
    }

    /// 返回当前监视点数量
    pub fn watch_count(&self) -> usize {
        self.watches.len()
    }

    /// 返回指定从站的监视点数量
    pub fn watch_count_for_slave(&self, slave_index: u16) -> usize {
        self.watches.keys().filter(|(s, _)| *s == slave_index).count()
    }

    /// 列出所有监视点 (从站索引, 名称)
    pub fn watch_list(&self) -> Vec<(u16, &str)> {
        self.watches.keys()
            .map(|(s, n)| (*s, n.as_str()))
            .collect()
    }

    // ===================== 内部辅助 =====================

    /// 直接从 IOmap 读取从站输入字节范围
    fn read_input_bytes(&self, slave_index: u16, offset: u32, count: u32) -> Result<Vec<u8>> {
        let mut out_size: c_int = 0;
        let mut out_ptr: *mut u8 = std::ptr::null_mut();
        let mut in_size: c_int = 0;
        let mut in_ptr: *mut u8 = std::ptr::null_mut();

        let ok = unsafe {
            ffi::GetIO(self.master_index, slave_index,
                       &mut out_size, &mut out_ptr,
                       &mut in_size, &mut in_ptr)
        };
        if ok == 0 || in_ptr.is_null() || in_size <= 0 {
            return Err(crate::data::error::DarraError::NullPointer);
        }
        let available = in_size as u32;
        let end = offset.saturating_add(count).min(available);
        if offset >= end {
            return Ok(vec![0u8; count as usize]);
        }
        let actual_count = (end - offset) as usize;
        let mut buf = vec![0u8; count as usize];
        unsafe {
            std::ptr::copy_nonoverlapping(
                in_ptr.add(offset as usize),
                buf.as_mut_ptr(),
                actual_count,
            );
        }
        Ok(buf)
    }
}

impl std::fmt::Debug for PdoMonitor {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("PdoMonitor")
            .field("master_index", &self.master_index)
            .field("watch_count", &self.watch_count())
            .field("total_changes", &self.total_changes)
            .finish()
    }
}

// ===================== 扩展工具：u16/u32 字段变化检测 =====================

/// 从字节切片读取小端 u16（偏移 offset，越界返回 0）
pub fn read_le_u16(data: &[u8], offset: usize) -> u16 {
    // 修复: 边界检查应使用 > 而非 >=，否则刚好够读时会误判越界
    if offset + 2 > data.len() { return 0; }
    u16::from_le_bytes([data[offset], data[offset + 1]])
}

/// 从字节切片读取小端 u32（偏移 offset，越界返回 0）
pub fn read_le_u32(data: &[u8], offset: usize) -> u32 {
    // 修复: 边界检查应使用 > 而非 >=，否则刚好够读时会误判越界
    if offset + 4 > data.len() { return 0; }
    u32::from_le_bytes([data[offset], data[offset + 1], data[offset + 2], data[offset + 3]])
}

/// 从字节切片读取小端 i16（偏移 offset，越界返回 0）
pub fn read_le_i16(data: &[u8], offset: usize) -> i16 {
    read_le_u16(data, offset) as i16
}

/// 从字节切片读取小端 i32（偏移 offset，越界返回 0）
pub fn read_le_i32(data: &[u8], offset: usize) -> i32 {
    read_le_u32(data, offset) as i32
}

// ===================== PDODataItem =====================

/// 单个 PDO 数据项 - 类型化访问
///
/// 对应 C# PDODataItem: Content, AsInt16, AsInt32, AsUInt16, AsUInt32,
/// AsFloat, AsDouble, GetBit, SetBit
pub struct PdoDataItem {
    /// 基础指针
    base_ptr: *mut u8,
    /// 在基础指针中的字节偏移
    offset: usize,
    /// 是否为输入 PDO (只读)
    is_input: bool,
}

impl PdoDataItem {
    /// 创建新的 PDO 数据项
    pub fn new(base_ptr: *mut u8, offset: usize, is_input: bool) -> Self {
        Self { base_ptr, offset, is_input }
    }

    /// 校验指针有效性
    fn validate(&self) -> *mut u8 {
        assert!(!self.base_ptr.is_null(), "基础指针为空");
        unsafe { self.base_ptr.add(self.offset) }
    }

    /// 检查是否可写
    fn check_writable(&self) {
        assert!(!self.is_input, "不能写入输入 PDO");
    }

    // ---- 字节访问 ----

    /// 获取字节值
    pub fn content(&self) -> u8 {
        unsafe { *self.validate() }
    }

    /// 设置字节值
    pub fn set_content(&self, value: u8) {
        self.check_writable();
        unsafe { *self.validate() = value; }
    }

    // ---- Int16 访问 ----

    /// 获取 Int16 值 (小端)
    pub fn as_int16(&self) -> i16 {
        let ptr = self.validate();
        unsafe { (ptr as *const i16).read_unaligned() }
    }

    /// 设置 Int16 值 (小端)
    pub fn set_int16(&self, value: i16) {
        self.check_writable();
        let ptr = self.validate();
        unsafe { (ptr as *mut i16).write_unaligned(value); }
    }

    // ---- UInt16 访问 ----

    /// 获取 UInt16 值 (小端)
    pub fn as_uint16(&self) -> u16 {
        let ptr = self.validate();
        unsafe { (ptr as *const u16).read_unaligned() }
    }

    /// 设置 UInt16 值 (小端)
    pub fn set_uint16(&self, value: u16) {
        self.check_writable();
        let ptr = self.validate();
        unsafe { (ptr as *mut u16).write_unaligned(value); }
    }

    // ---- Int32 访问 ----

    /// 获取 Int32 值 (小端)
    pub fn as_int32(&self) -> i32 {
        let ptr = self.validate();
        unsafe { (ptr as *const i32).read_unaligned() }
    }

    /// 设置 Int32 值 (小端)
    pub fn set_int32(&self, value: i32) {
        self.check_writable();
        let ptr = self.validate();
        unsafe { (ptr as *mut i32).write_unaligned(value); }
    }

    // ---- UInt32 访问 ----

    /// 获取 UInt32 值 (小端)
    pub fn as_uint32(&self) -> u32 {
        let ptr = self.validate();
        unsafe { (ptr as *const u32).read_unaligned() }
    }

    /// 设置 UInt32 值 (小端)
    pub fn set_uint32(&self, value: u32) {
        self.check_writable();
        let ptr = self.validate();
        unsafe { (ptr as *mut u32).write_unaligned(value); }
    }

    // ---- Float 访问 ----

    /// 获取 Float 值 (小端)
    pub fn as_float(&self) -> f32 {
        let ptr = self.validate();
        unsafe { (ptr as *const f32).read_unaligned() }
    }

    /// 设置 Float 值 (小端)
    pub fn set_float(&self, value: f32) {
        self.check_writable();
        let ptr = self.validate();
        unsafe { (ptr as *mut f32).write_unaligned(value); }
    }

    // ---- Double 访问 ----

    /// 获取 Double 值 (小端)
    pub fn as_double(&self) -> f64 {
        let ptr = self.validate();
        unsafe { (ptr as *const f64).read_unaligned() }
    }

    /// 设置 Double 值 (小端)
    pub fn set_double(&self, value: f64) {
        self.check_writable();
        let ptr = self.validate();
        unsafe { (ptr as *mut f64).write_unaligned(value); }
    }

    // ---- 位操作 ----

    /// 获取指定索引处的位值 (0-7)
    pub fn get_bit(&self, bit_index: u8) -> bool {
        assert!(bit_index <= 7, "位索引必须在 0-7 之间");
        (self.content() & (1 << bit_index)) != 0
    }

    /// 设置指定索引处的位值 (0-7)
    pub fn set_bit(&self, bit_index: u8, value: bool) {
        self.check_writable();
        assert!(bit_index <= 7, "位索引必须在 0-7 之间");
        let mut current = self.content();
        if value {
            current |= 1 << bit_index;
        } else {
            current &= !(1 << bit_index);
        }
        self.set_content(current);
    }
}

// ===================== PDOArrayInstance =====================

/// 基于数组的 PDO 数据访问
///
/// 对应 C# PDOArrayInstance
/// 通过索引器按字节访问 PDO 数据区。
pub struct PdoArrayInstance {
    /// 数据指针
    data_ptr: *mut u8,
    /// 数据长度（字节）
    length: usize,
    /// 是否为输入 PDO (只读)
    is_input: bool,
}

impl PdoArrayInstance {
    /// 创建新的 PDO 数组实例
    pub fn new(data_ptr: *mut u8, length: usize, is_input: bool) -> Self {
        Self { data_ptr, length, is_input }
    }

    /// 按字节索引获取 PdoDataItem
    pub fn get(&self, index: usize) -> PdoDataItem {
        assert!(index < self.length, "索引 {} 超出范围 [0, {})", index, self.length);
        PdoDataItem::new(self.data_ptr, index, self.is_input)
    }

    /// 按字节索引写入单字节
    pub fn set(&self, index: usize, value: u8) {
        assert!(index < self.length, "索引 {} 超出范围 [0, {})", index, self.length);
        assert!(!self.is_input, "不能写入输入 PDO");
        let item = PdoDataItem::new(self.data_ptr, index, self.is_input);
        item.set_content(value);
    }

    /// 获取 PDO 数据总长度（字节）
    pub fn length(&self) -> usize {
        self.length
    }
}

// ===================== 从站 PDO 零拷贝扩展 =====================

/// PDO 输入区视图.
///
/// 不直接暴露可空裸指针; 需要零拷贝读取时, 调用者必须在 unsafe 中把视图转换为切片,
/// 并保证 IOmap 未在使用期间重新映射.
#[derive(Debug, Clone, Copy)]
pub struct PdoInputView {
    ptr: NonNull<u8>,
    len: usize,
}

impl PdoInputView {
    /// 数据长度 (字节)
    pub fn len(&self) -> usize {
        self.len
    }

    /// 是否为空
    pub fn is_empty(&self) -> bool {
        self.len == 0
    }

    /// 非空指针, 供必须对接原生内存的调用方使用
    pub fn as_ptr(&self) -> *const u8 {
        self.ptr.as_ptr()
    }

    /// 转成不可变切片.
    ///
    /// # 安全性
    /// 返回切片直接引用 IOmap 内存; 调用方必须保证主站生命周期、PDO 循环和 IOmap 映射在切片使用期间有效.
    pub unsafe fn as_slice<'a>(&self) -> &'a [u8] {
        std::slice::from_raw_parts(self.ptr.as_ptr(), self.len)
    }
}

/// PDO 输出区视图.
///
/// 不直接暴露可空裸指针; 需要零拷贝写入时, 调用者必须在 unsafe 中把视图转换为可变切片,
/// 并保证没有并发写入同一 IOmap 区域.
#[derive(Debug, Clone, Copy)]
pub struct PdoOutputView {
    ptr: NonNull<u8>,
    len: usize,
}

impl PdoOutputView {
    /// 数据长度 (字节)
    pub fn len(&self) -> usize {
        self.len
    }

    /// 是否为空
    pub fn is_empty(&self) -> bool {
        self.len == 0
    }

    /// 非空指针, 供必须对接原生内存的调用方使用
    pub fn as_ptr(&self) -> *mut u8 {
        self.ptr.as_ptr()
    }

    /// 转成可变切片.
    ///
    /// # 安全性
    /// 返回切片直接引用 IOmap 内存; 调用方必须保证主站生命周期、PDO 循环和 IOmap 映射在切片使用期间有效,
    /// 且同一区域没有其他可变引用.
    pub unsafe fn as_mut_slice<'a>(&self) -> &'a mut [u8] {
        std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len)
    }
}

fn io_pointers(master_index: u16, slave_index: u16) -> Option<(usize, *mut u8, usize, *mut u8)> {
    let mut out_size: c_int = 0;
    let mut out_ptr: *mut u8 = std::ptr::null_mut();
    let mut in_size: c_int = 0;
    let mut in_ptr: *mut u8 = std::ptr::null_mut();

    let ok = unsafe {
        ffi::GetIO(
            master_index,
            slave_index,
            &mut out_size,
            &mut out_ptr,
            &mut in_size,
            &mut in_ptr,
        )
    };
    if ok != 1 {
        return None;
    }

    let out_len = if out_size > 0 { out_size as usize } else { 0 };
    let in_len = if in_size > 0 { in_size as usize } else { 0 };
    Some((out_len, out_ptr, in_len, in_ptr))
}

fn copy_len(total: usize, offset: usize, requested: usize, user_len: usize) -> usize {
    let Some(available) = total.checked_sub(offset) else {
        return 0;
    };
    let requested = if requested == 0 {
        available
    } else {
        requested.min(available)
    };
    requested.min(user_len)
}

fn pdo_unavailable(name: &str) -> DarraError {
    DarraError::PdoFailed(format!("{} 原生符号不可用", name))
}

/// 获取输入数据的安全视图.
///
/// 返回 `None` 表示 `GetIO` 失败、输入区为空或 DLL 尚未建立 IOmap.
pub fn input_data_view(master_index: u16, slave_index: u16) -> Option<PdoInputView> {
    let (_, _, in_len, in_ptr) = io_pointers(master_index, slave_index)?;
    if in_len == 0 {
        return None;
    }
    NonNull::new(in_ptr).map(|ptr| PdoInputView { ptr, len: in_len })
}

/// 获取输出数据的安全视图.
///
/// 返回 `None` 表示 `GetIO` 失败、输出区为空或 DLL 尚未建立 IOmap.
pub fn output_data_view(master_index: u16, slave_index: u16) -> Option<PdoOutputView> {
    let (out_len, out_ptr, _, _) = io_pointers(master_index, slave_index)?;
    if out_len == 0 {
        return None;
    }
    NonNull::new(out_ptr).map(|ptr| PdoOutputView { ptr, len: out_len })
}

/// 获取输入数据的直接指针（零拷贝访问）
///
/// 警告: 仅在 PDO 循环中使用, 指针在 IOmap 重新映射后会失效
pub fn get_input_data_pointer(master_index: u16, slave_index: u16) -> *mut u8 {
    input_data_view(master_index, slave_index)
        .map(|view| view.as_ptr() as *mut u8)
        .unwrap_or(std::ptr::null_mut())
}

/// 获取输出数据的直接指针（零拷贝访问）
///
/// 警告: 仅在 PDO 循环中使用, 指针在 IOmap 重新映射后会失效
pub fn get_output_data_pointer(master_index: u16, slave_index: u16) -> *mut u8 {
    output_data_view(master_index, slave_index)
        .map(|view| view.as_ptr())
        .unwrap_or(std::ptr::null_mut())
}

/// 读取输入数据（直接从 IOmap）
///
/// 返回实际读取的字节数
pub fn read_input_data_direct(master_index: u16, slave_index: u16,
                              buffer: &mut [u8], offset: usize, length: usize) -> usize {
    try_read_input_data_direct(master_index, slave_index, buffer, offset, length).unwrap_or(0)
}

/// 读取输入数据（直接从 IOmap），失败时返回明确错误.
///
/// `offset` 是从站输入区内的字节偏移; `length == 0` 表示读取到输入区末尾.
pub fn try_read_input_data_direct(
    master_index: u16,
    slave_index: u16,
    buffer: &mut [u8],
    offset: usize,
    length: usize,
) -> Result<usize> {
    let view = input_data_view(master_index, slave_index).ok_or(DarraError::NullPointer)?;
    let to_read = copy_len(view.len(), offset, length, buffer.len());
    if to_read == 0 {
        return Ok(0);
    }
    unsafe {
        std::ptr::copy_nonoverlapping(view.as_ptr().add(offset), buffer.as_mut_ptr(), to_read);
    }
    Ok(to_read)
}

// ===================== PDO 统计管理 =====================

/// PDO 管理器 - 提供主站级 PDO 统计信息
pub struct PdoManager {
    /// 主站索引
    master_index: u16,
}

impl PdoManager {
    /// 创建新的 PDO 管理器
    pub fn new(master_index: u16) -> Self {
        Self { master_index }
    }

    /// 获取平均周期时间（纳秒）
    ///
    /// 返回 `None` 表示当前运行时 DLL 未导出该诊断符号.
    pub fn avg_cycle_time_ns(&self) -> Option<u64> {
        if ffi::dynamic_ffi::ffi_gap().get_pdo_avg_cycle_time_ns.is_none() {
            return None;
        }
        Some(unsafe { ffi::GetPDOAvgCycleTimeNs(self.master_index) })
    }

    /// 获取 PDO 错误计数
    ///
    /// 返回 `None` 表示当前运行时 DLL 未导出该诊断符号.
    pub fn error_count(&self) -> Option<u32> {
        if ffi::dynamic_ffi::ffi_gap().get_pdo_error_count.is_none() {
            return None;
        }
        Some(unsafe { ffi::GetPDOErrorCount(self.master_index) })
    }

    /// 检查 PDO 数据是否有效（至少接收到一帧且 WKC 正确）
    ///
    /// 返回 `None` 表示当前运行时 DLL 未导出该诊断符号.
    pub fn is_valid(&self) -> Option<bool> {
        if ffi::dynamic_ffi::ffi_gap().is_pdo_valid.is_none() {
            return None;
        }
        Some(unsafe { ffi::IsPDOValid(self.master_index) != 0 })
    }

    /// 检查 PDO 数据是否发生变化（自上次调用后）
    ///
    /// 返回 `None` 表示当前运行时 DLL 未导出该诊断符号.
    pub fn changed(&self) -> Option<bool> {
        if ffi::dynamic_ffi::ffi_gap().is_pdo_changed.is_none() {
            return None;
        }
        Some(unsafe { ffi::IsPDOChanged(self.master_index) != 0 })
    }

    /// 获取 PDO 预期数据大小（所有从站输入+输出总字节数）
    ///
    /// 返回 `None` 表示当前运行时 DLL 未导出该诊断符号.
    pub fn expected_size(&self) -> Option<u32> {
        if ffi::dynamic_ffi::ffi_gap().get_pdo_expected_size.is_none() {
            return None;
        }
        Some(unsafe { ffi::GetPDOExpectedSize(self.master_index) })
    }
}

/// 写入输出数据（直接到 IOmap）
///
/// 返回实际写入的字节数
pub fn write_output_data_direct(master_index: u16, slave_index: u16,
                                data: &[u8], offset: usize, length: usize) -> usize {
    try_write_output_data_direct(master_index, slave_index, data, offset, length).unwrap_or(0)
}

/// 写入输出数据（直接到 IOmap），失败时返回明确错误.
///
/// `offset` 是从站输出区内的字节偏移; `length == 0` 表示写入 `data` 全部可写内容.
pub fn try_write_output_data_direct(
    master_index: u16,
    slave_index: u16,
    data: &[u8],
    offset: usize,
    length: usize,
) -> Result<usize> {
    let view = output_data_view(master_index, slave_index).ok_or(DarraError::NullPointer)?;
    let to_write = copy_len(view.len(), offset, length, data.len());
    if to_write == 0 {
        return Ok(0);
    }
    unsafe {
        std::ptr::copy_nonoverlapping(data.as_ptr(), view.as_ptr().add(offset), to_write);
    }
    Ok(to_write)
}

// ===================== 批量 PDO 读写 =====================

/// 批量读取多个 PDO 值 (零拷贝, 返回 &[u8] 切片)
///
/// 通过一次 GetIO 调用获取输入数据指针, 然后按偏移量切片,
/// 避免多次 FFI 调用。返回的切片直接引用 IOmap 内存。
///
/// # 参数
/// - `master_index`: 主站索引
/// - `slave_index`: 从站索引
/// - `offsets`: 各 PDO 项在输入数据区的字节偏移
/// - `sizes`: 各 PDO 项的字节长度
///
/// # 返回
/// 各 PDO 项的字节数据, 读取失败的项为空 Vec
pub fn batch_read(master_index: u16, slave_index: u16,
                  offsets: &[usize], sizes: &[usize]) -> Vec<Vec<u8>> {
    assert_eq!(offsets.len(), sizes.len(), "offsets 和 sizes 长度必须一致");

    let mut results: Vec<Vec<u8>> = vec![Vec::new(); offsets.len()];

    let Some(view) = input_data_view(master_index, slave_index) else {
        return results;
    };

    let total = view.len();
    for (i, (&offset, &size)) in offsets.iter().zip(sizes.iter()).enumerate() {
        if offset.checked_add(size).is_some_and(|end| end <= total) {
            let slice = unsafe { std::slice::from_raw_parts(view.as_ptr().add(offset), size) };
            results[i] = slice.to_vec();
        }
    }
    results
}

/// 批量写入多个 PDO 值 (零拷贝, 直接写入 IOmap)
///
/// 通过一次 GetIO 调用获取输出数据指针, 然后按偏移量依次写入。
///
/// # 参数
/// - `master_index`: 主站索引
/// - `slave_index`: 从站索引
/// - `offsets`: 各 PDO 项在输出数据区的字节偏移
/// - `values`: 各 PDO 项要写入的字节数据
///
/// # 返回
/// 成功写入的 PDO 项数量
pub fn batch_write(master_index: u16, slave_index: u16,
                   offsets: &[usize], values: &[&[u8]]) -> usize {
    assert_eq!(offsets.len(), values.len(), "offsets 和 values 长度必须一致");

    let Some(view) = output_data_view(master_index, slave_index) else {
        return 0;
    };

    let total = view.len();
    let mut written = 0usize;
    for (&offset, data) in offsets.iter().zip(values.iter()) {
        if offset.checked_add(data.len()).is_some_and(|end| end <= total) {
            unsafe {
                std::ptr::copy_nonoverlapping(data.as_ptr(), view.as_ptr().add(offset), data.len());
            }
            written += 1;
        }
    }
    written
}

// ===================== PDO 零拷贝内存视图 =====================

/// 获取输入数据的切片引用（零拷贝访问）
///
/// 对应 C# GetInputDataSpan / Python get_input_memoryview
///
/// # 安全性
/// 返回的切片直接指向 IOmap 内存，IOmap 重新映射后切片会失效。
/// 调用者必须确保在 PDO 循环中使用。
pub unsafe fn input_slice(master_index: u16, slave_index: u16) -> &'static [u8] {
    match input_data_view(master_index, slave_index) {
        Some(view) => view.as_slice(),
        None => &[],
    }
}

/// 获取输出数据的切片引用（零拷贝访问）
///
/// 对应 C# GetOutputDataSpan / Python get_output_memoryview
///
/// # 安全性
/// 返回的切片直接指向 IOmap 内存，IOmap 重新映射后切片会失效。
pub unsafe fn output_slice(master_index: u16, slave_index: u16) -> &'static mut [u8] {
    match output_data_view(master_index, slave_index) {
        Some(view) => view.as_mut_slice(),
        None => &mut [],
    }
}

// ===================== PDO 性能统计 =====================

/// PDO 性能统计数据
#[derive(Debug, Clone, Default)]
pub struct PdoPerformanceStats {
    /// 读取计数
    pub read_count: u64,
    /// 写入计数
    pub write_count: u64,
    /// 错误计数
    pub error_count: u64,
    /// 总读取字节数
    pub total_bytes_read: u64,
    /// 总写入字节数
    pub total_bytes_written: u64,
    /// 最后周期时间 (纳秒)
    pub last_cycle_ns: u64,
    /// 最小周期时间 (纳秒)
    pub min_cycle_ns: u64,
    /// 最大周期时间 (纳秒)
    pub max_cycle_ns: u64,
    /// 平均周期时间 (纳秒)
    pub avg_cycle_ns: u64,
}

/// 获取从站的 PDO 性能统计
///
/// 对应 C# GetPerformanceStats.
///
/// 返回 `None` 表示当前运行时 DLL 未导出 `GetPDOStats`, 或 native 返回 null.
pub fn performance_stats(master_index: u16, slave_index: u16) -> Option<PdoPerformanceStats> {
    if ffi::dynamic_ffi::ffi_gap().get_pdo_stats.is_none() {
        return None;
    }
    unsafe {
        let ptr = ffi::GetPDOStats(master_index, slave_index);
        if ptr.is_null() {
            return None;
        }
        // 修复: DLL 返回的是 PdoStats (packed 结构体，前5个字段是 u32，后4个是 u64)
        // 之前错误地将整个区域当作 [u64;8] 读取，导致字段错位
        let stats = std::ptr::read_unaligned(ptr as *const crate::data::structures::PdoStats);
        Some(PdoPerformanceStats {
            read_count: stats.read_count as u64,
            write_count: stats.write_count as u64,
            error_count: stats.error_count as u64,
            total_bytes_read: stats.total_bytes_read as u64,
            total_bytes_written: stats.total_bytes_written as u64,
            last_cycle_ns: stats.last_cycle_time_ns,
            min_cycle_ns: stats.min_cycle_time_ns,
            max_cycle_ns: stats.max_cycle_time_ns,
            avg_cycle_ns: stats.avg_cycle_time_ns,
        })
    }
}

/// 重置从站的性能统计
///
/// 对应 C# ResetPerformanceStats.
///
/// 当前运行时 DLL 未导出 `ResetPDOStats` 时返回明确错误.
pub fn reset_performance_stats(master_index: u16, slave_index: u16) -> Result<()> {
    if ffi::dynamic_ffi::ffi_gap().reset_pdo_stats.is_none() {
        return Err(pdo_unavailable("ResetPDOStats"));
    }
    unsafe { ffi::ResetPDOStats(master_index, slave_index); }
    Ok(())
}

// ===================== 池化批量读写 =====================

/// 执行批量读取（复用预分配 Vec，减少内存分配）
///
/// 对应 C# BatchReadPooled
pub fn batch_read_pooled(master_index: u16, slave_indices: &[u16],
                          expected_sizes: &[usize]) -> Vec<Option<Vec<u8>>> {
    assert_eq!(slave_indices.len(), expected_sizes.len());
    let mut results: Vec<Option<Vec<u8>>> = vec![None; slave_indices.len()];

    for (i, (&si, &sz)) in slave_indices.iter().zip(expected_sizes.iter()).enumerate() {
        if let Some(view) = input_data_view(master_index, si) {
            let to_read = sz.min(view.len());
            let mut buf = vec![0u8; to_read];
            unsafe { std::ptr::copy_nonoverlapping(view.as_ptr(), buf.as_mut_ptr(), to_read); }
            results[i] = Some(buf);
        }
    }
    results
}

// ===================== AsSpan / ReadInputToBuffer / WriteOutputFromBuffer / WriteFromTyped / EnsureCapacity =====================

/// 获取 PDO 数据区的不可变切片 (对应 C# AsSpan)
///
/// # 安全性
/// 返回的切片直接指向 IOmap 内存, IOmap 重新映射后失效。
pub unsafe fn as_slice(master_index: u16, slave_index: u16, is_input: bool) -> &'static [u8] {
    if is_input {
        input_slice(master_index, slave_index)
    } else {
        output_slice(master_index, slave_index)
    }
}

/// 读取 PDO 输入数据到用户 buffer (对应 C# ReadInputDataToSpan)
pub fn read_input_to_buffer(master_index: u16, slave_index: u16, buf: &mut [u8]) -> usize {
    read_input_data_direct(master_index, slave_index, buf, 0, buf.len())
}

/// 从用户 buffer 写入 PDO 输出数据 (对应 C# WriteOutputDataFromSpan)
pub fn write_output_from_buffer(master_index: u16, slave_index: u16, buf: &[u8]) -> usize {
    write_output_data_direct(master_index, slave_index, buf, 0, buf.len())
}

/// 从类型化数据写入 PDO 输出 (对应 C# WriteFromBaseData)
///
/// 将任意类型 T 的字节表示直接写入输出 IOmap。
///
/// # 安全性
/// T 必须是 POD 类型 (无填充、无指针)。
pub unsafe fn write_from_typed<T: Copy>(master_index: u16, slave_index: u16, value: &T, byte_offset: usize) -> bool {
    let size = std::mem::size_of::<T>();
    let data = std::slice::from_raw_parts(value as *const T as *const u8, size);
    let mut out_size: c_int = 0;
    let mut out_ptr: *mut u8 = std::ptr::null_mut();
    let mut in_size: c_int = 0;
    let mut in_ptr: *mut u8 = std::ptr::null_mut();

    let ok = ffi::GetIO(master_index, slave_index,
                        &mut out_size, &mut out_ptr, &mut in_size, &mut in_ptr);
    if ok != 1 || out_ptr.is_null() || out_size <= 0 {
        return false;
    }
    if byte_offset + size > out_size as usize {
        return false;
    }
    std::ptr::copy_nonoverlapping(data.as_ptr(), out_ptr.add(byte_offset), size);
    true
}

/// 确保 Vec 容量足够 (对应 C# EnsureCapacity)
pub fn ensure_capacity(buf: &mut Vec<u8>, required_size: usize) {
    if buf.len() < required_size {
        buf.resize(required_size, 0);
    }
}

/// 执行批量写入（复用预分配缓冲区）
///
/// 对应 C# BatchWritePooled
pub fn batch_write_pooled(master_index: u16, slave_indices: &[u16],
                           data_list: &[&[u8]]) -> usize {
    assert_eq!(slave_indices.len(), data_list.len());
    let mut success = 0usize;

    for (&si, data) in slave_indices.iter().zip(data_list.iter()) {
        if let Some(view) = output_data_view(master_index, si) {
            let to_write = data.len().min(view.len());
            unsafe { std::ptr::copy_nonoverlapping(data.as_ptr(), view.as_ptr(), to_write); }
            success += 1;
        }
    }
    success
}
