//! ESI 工具函数
//!
//! 对应 C# Utils/ESI.cs
//! 提供 ESI 文件解析辅助功能。
//! 注: 主要的 ESI 加载器在 slave/esi.rs 中，
//! 此模块提供 Utils/ESI.cs 中的辅助类型。

/// ESI 端口物理类型
///
/// 对应 C# ESIPhysicsPortType
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EsiPhysicsPortType {
    /// 未使用
    NotUsed = 0,
    /// MII (100Mbit Ethernet)
    Mii = 1,
    /// E-Bus
    EBus = 2,
    /// Hot Connect
    HotConnect = 3,
}

impl From<u8> for EsiPhysicsPortType {
    fn from(val: u8) -> Self {
        match val {
            0 => Self::NotUsed,
            1 => Self::Mii,
            2 => Self::EBus,
            3 => Self::HotConnect,
            _ => Self::NotUsed,
        }
    }
}

/// ESI 版本检查策略
///
/// 对应 C# ESIRevisionCheckStrategy
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EsiRevisionCheckStrategy {
    /// 不检查版本号
    None,
    /// 精确匹配
    Eq,
    /// 等于或更大
    EqOrG,
    /// 低字相等
    LwEq,
    /// 高字相等
    HwEq,
    /// 低字等于或更大
    LwEqOrG,
    /// 高字等于或更大
    HwEqOrG,
}

/// 解析十六进制或十进制的 u32 值
///
/// 对应 C# TryParseUIntHexOrDec
pub fn parse_uint_hex_or_dec(s: &str) -> Option<u32> {
    let s = s.trim();
    if s.is_empty() {
        return None;
    }

    // 十六进制前缀
    if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
        return u32::from_str_radix(hex, 16).ok();
    }
    if let Some(hex) = s.strip_prefix("#x").or_else(|| s.strip_prefix("#X")) {
        return u32::from_str_radix(hex, 16).ok();
    }
    if let Some(hex) = s.strip_prefix('#') {
        return u32::from_str_radix(hex, 16).ok();
    }

    // 尝试十进制
    if let Ok(val) = s.parse::<u32>() {
        return Some(val);
    }

    // 尝试十六进制（纯十六进制字符）
    if s.chars().all(|c| c.is_ascii_hexdigit()) && s.chars().any(|c| c.is_ascii_alphabetic()) {
        return u32::from_str_radix(s, 16).ok();
    }

    None
}

/// 解析十六进制或十进制的 u16 值
pub fn parse_u16_hex_or_dec(s: &str) -> Option<u16> {
    parse_uint_hex_or_dec(s).map(|v| v as u16)
}

// ===================== ESI 数据模型 =====================

/// Bootstrap 邮箱配置
#[derive(Debug, Clone, Default)]
pub struct EsiBootstrapInfo {
    pub recv_mailbox_offset: u16,
    pub recv_mailbox_size: u16,
    pub send_mailbox_offset: u16,
    pub send_mailbox_size: u16,
}

/// CoE 详细配置
#[derive(Debug, Clone, Default)]
pub struct EsiCoEDetails {
    pub complete_access: bool,
    pub diag_history: bool,
    pub pdo_assign: bool,
    pub pdo_config: bool,
    pub sdo_info: bool,
    pub segmented_sdo: bool,
}

/// DC 操作模式
#[derive(Debug, Clone, Default)]
pub struct EsiDcOpMode {
    pub assign_activate: u16,
    pub cycle_time_sync0: u32,
    pub cycle_time_sync0_factor: i32,
    pub cycle_time_sync1: u32,
    pub cycle_time_sync1_factor: i32,
    pub description: String,
    pub name: String,
    pub shift_time_sync0: i32,
    pub shift_time_sync1: i32,
}

impl EsiDcOpMode {
    /// 是否为 DC 同步模式
    pub fn is_dc_sync(&self) -> bool { self.assign_activate != 0 }
    /// 是否启用 SYNC0
    pub fn sync0_enabled(&self) -> bool { (self.assign_activate & 0x0100) != 0 }
    /// 是否启用 SYNC1
    pub fn sync1_enabled(&self) -> bool { (self.assign_activate & 0x0400) != 0 }
}

/// DC 配置
#[derive(Debug, Clone, Default)]
pub struct EsiDcConfiguration {
    pub op_modes: Vec<EsiDcOpMode>,
    pub unknown_64bit: bool,
}

impl EsiDcConfiguration {
    /// 获取 DC 同步操作模式
    pub fn dc_sync_op_mode(&self) -> Option<&EsiDcOpMode> {
        self.op_modes.iter().find(|m| m.assign_activate != 0)
    }
}

/// EEPROM 配置
#[derive(Debug, Clone, Default)]
pub struct EsiEepromConfiguration {
    pub bootstrap: Option<EsiBootstrapInfo>,
    pub byte_size: i32,
    pub config_data: String,
}

/// 电气信息
#[derive(Debug, Clone, Default)]
pub struct EsiElectricalInfo {
    pub ebus_current: i32,
}

impl EsiElectricalInfo {
    pub fn is_power_supply(&self) -> bool { self.ebus_current < 0 }
    pub fn power_consumption(&self) -> i32 { if self.is_power_supply() { 0 } else { self.ebus_current } }
}

/// 设备识别信息
#[derive(Debug, Clone, Default)]
pub struct EsiIdentification {
    pub identification_ado: u16,
    pub identification_value: u16,
}

/// 邮箱超时配置
#[derive(Debug, Clone)]
pub struct EsiMailboxTimeout {
    pub request_timeout: i32,
    pub response_timeout: i32,
}

impl Default for EsiMailboxTimeout {
    fn default() -> Self { Self { request_timeout: 100, response_timeout: 2000 } }
}

/// PDO 条目信息
#[derive(Debug, Clone, Default)]
pub struct EsiPdoEntry {
    pub bit_length: u8,
    pub data_type: String,
    pub index: u16,
    pub name: String,
    pub sub_index: u8,
}

/// PDO 信息
#[derive(Debug, Clone, Default)]
pub struct EsiPdoInfo {
    pub entries: Vec<EsiPdoEntry>,
    pub exclude_indices: Vec<u16>,
    pub index: u16,
    pub is_fixed: bool,
    pub is_mandatory: bool,
    pub name: String,
    pub sync_manager: u8,
}

/// PDO 配置信息
#[derive(Debug, Clone, Default)]
pub struct EsiPdoConfiguration {
    pub rx_pdos: Vec<EsiPdoInfo>,
    pub tx_pdos: Vec<EsiPdoInfo>,
}

/// Physics 端口信息
#[derive(Debug, Clone)]
pub struct EsiPhysicsPort {
    pub code: char,
    pub description: String,
    pub index: usize,
    pub port_type: EsiPhysicsPortType,
}

/// Physics 属性解析结果
#[derive(Debug, Clone, Default)]
pub struct EsiPhysicsInfo {
    pub ports: Vec<EsiPhysicsPort>,
    pub raw_value: String,
}

impl EsiPhysicsInfo {
    /// 解析 Physics 字符串
    pub fn parse(physics: &str) -> Self {
        let mut info = Self { raw_value: physics.to_string(), ports: Vec::new() };
        for (i, ch) in physics.chars().enumerate() {
            let (pt, desc) = match ch.to_ascii_uppercase() {
                'Y' => (EsiPhysicsPortType::Mii, "MII (100Mbit)"),
                'K' => (EsiPhysicsPortType::EBus, "E-Bus"),
                'H' => (EsiPhysicsPortType::HotConnect, "Hot Connect"),
                _ => (EsiPhysicsPortType::NotUsed, "Not Used"),
            };
            info.ports.push(EsiPhysicsPort { code: ch, description: desc.to_string(), index: i, port_type: pt });
        }
        info
    }
}

/// 端口信息
#[derive(Debug, Clone, Default)]
pub struct EsiPortInfo {
    pub index: i32,
    pub label: String,
    pub port_type: String,
}

/// 启动 SDO 配置
#[derive(Debug, Clone, Default)]
pub struct EsiStartupSdo {
    pub data_type: String,
    pub index: u16,
    pub name: String,
    pub sub_index: u8,
    pub value: String,
}

/// ESI 统计信息
#[derive(Debug, Clone, Default)]
pub struct EsiStatistics {
    pub total_devices: i32,
    pub total_files: i32,
    pub vendors: Vec<String>,
}

/// SyncManager 信息
#[derive(Debug, Clone, Default)]
pub struct EsiSyncManagerInfo {
    pub control_byte: u8,
    pub default_size: u16,
    pub enable: bool,
    pub index: i32,
    pub max_size: u16,
    pub min_size: u16,
    pub name: String,
    pub start_address: u16,
}

impl EsiSyncManagerInfo {
    /// SDO 索引 (0x1C10 + SM索引)
    pub fn sdo_index(&self) -> u16 { 0x1C10 + self.index as u16 }
}

/// ESI 设备详细信息
#[derive(Debug, Clone, Default)]
pub struct EsiDeviceInfo {
    pub coe_details: Option<EsiCoEDetails>,
    pub dc_configuration: Option<EsiDcConfiguration>,
    pub device_class: String,
    pub eeprom_configuration: Option<EsiEepromConfiguration>,
    pub electrical: Option<EsiElectricalInfo>,
    pub file_name: String,
    pub group_type: String,
    pub identification: Option<EsiIdentification>,
    pub mailbox_timeout: Option<EsiMailboxTimeout>,
    pub physics: String,
    pub physics_info: Option<EsiPhysicsInfo>,
    pub ports: Vec<EsiPortInfo>,
    pub product_id: u32,
    pub product_name: String,
    pub product_text: String,
    pub product_url: String,
    pub profile_number: String,
    pub revision_check_strategy: EsiRevisionCheckStrategy,
    pub revision_id: u32,
    pub supports_coe: bool,
    pub supports_eoe: bool,
    pub supports_foe: bool,
    pub supports_soe: bool,
    pub supports_voe: bool,
    pub supports_frame_repeat: bool,
    pub vendor_comment: String,
    pub vendor_id: u32,
    pub vendor_name: String,
}

impl EsiDeviceInfo {
    /// DC AssignActivate 值
    pub fn dc_assign_activate(&self) -> u16 {
        self.dc_configuration.as_ref()
            .and_then(|dc| dc.dc_sync_op_mode())
            .map(|m| m.assign_activate)
            .unwrap_or(0)
    }

    /// 是否支持 DC
    pub fn supports_dc(&self) -> bool {
        self.dc_configuration.as_ref()
            .map(|dc| dc.op_modes.iter().any(|m| m.is_dc_sync()))
            .unwrap_or(false)
    }

    /// 协议支持摘要
    pub fn protocol_summary(&self) -> String {
        let mut p = Vec::new();
        if self.supports_coe { p.push("CoE"); }
        if self.supports_foe { p.push("FoE"); }
        if self.supports_eoe { p.push("EoE"); }
        if self.supports_soe { p.push("SoE"); }
        if self.supports_voe { p.push("VoE"); }
        if p.is_empty() { "None".to_string() } else { p.join(", ") }
    }
}

impl Default for EsiRevisionCheckStrategy {
    fn default() -> Self { Self::None }
}

/// 计算 EEPROM CRC-8 (多项式 0x07, 初始值 0xFF)
pub fn calculate_eeprom_crc(data: &[u8], length: usize) -> u8 {
    let mut crc: u8 = 0xFF;
    for i in 0..length.min(data.len()) {
        crc ^= data[i];
        for _ in 0..8 {
            if crc & 0x80 != 0 {
                crc = (crc << 1) ^ 0x07;
            } else {
                crc <<= 1;
            }
        }
    }
    crc
}

/// 验证 EEPROM CRC
pub fn validate_eeprom_crc(eeprom_data: &[u8]) -> bool {
    if eeprom_data.len() < 15 { return false; }
    calculate_eeprom_crc(eeprom_data, 14) == eeprom_data[14]
}

/// 获取 ESI 配置数据的原始字节 (对应 C# GetConfigDataBytes)
///
/// 从 EEPROM 配置数据字符串中提取字节数组。
pub fn get_config_data_bytes(config_data_hex: &str) -> Vec<u8> {
    let hex = config_data_hex.trim();
    if hex.is_empty() {
        return Vec::new();
    }
    let mut result = Vec::with_capacity(hex.len() / 2);
    for i in (0..hex.len()).step_by(2) {
        if i + 1 < hex.len() {
            if let Ok(b) = u8::from_str_radix(&hex[i..i + 2], 16) {
                result.push(b);
            }
        }
    }
    result
}

/// 根据策略匹配版本号
pub fn match_revision(actual: u32, expected: u32, strategy: EsiRevisionCheckStrategy) -> bool {
    if expected == 0 { return true; }
    match strategy {
        EsiRevisionCheckStrategy::None => true,
        EsiRevisionCheckStrategy::Eq => actual == expected,
        EsiRevisionCheckStrategy::EqOrG => actual >= expected,
        EsiRevisionCheckStrategy::LwEq => (actual & 0xFFFF) == (expected & 0xFFFF),
        EsiRevisionCheckStrategy::HwEq => (actual >> 16) == (expected >> 16),
        EsiRevisionCheckStrategy::LwEqOrG => (actual & 0xFFFF) >= (expected & 0xFFFF),
        EsiRevisionCheckStrategy::HwEqOrG => (actual >> 16) >= (expected >> 16),
    }
}

// ============================================================================
// ESI 管理器 (静态门面, 对齐 C# EsiManager / DLL EcEsi_* 系列)
// ============================================================================

use std::collections::HashSet;
use std::ffi::CString;
use std::sync::Mutex;

static ESI_MANAGER_FILES: Mutex<Option<HashSet<String>>> = Mutex::new(None);

fn ensure_files_init(set: &mut Option<HashSet<String>>) -> &mut HashSet<String> {
    if set.is_none() {
        *set = Some(HashSet::new());
    }
    set.as_mut().unwrap()
}

/// ESI 管理器 (静态方法集合)
///
/// 对齐 C# `EsiManager` / DLL `EcEsi_*`:
/// - [`EsiManager::load_path`] 加载目录所有 .xml/.ESI (对齐 `EcEsi_LoadDirectory`)
/// - [`EsiManager::add_file`] 加载单个文件 (对齐 `EcEsi_LoadFile`)
/// - [`EsiManager::match_revision`] 版本号匹配
/// - [`EsiManager::bind_to_slave`] 绑定 ESI 到从站 (对齐 `EcEsi_BindToSlave`)
/// - [`EsiManager::apply_all_slaves`] 自动批量绑定 (对齐 `EcEsi_ApplyAllSlaves`)
/// - [`EsiManager::get_loaded_count`] (对齐 `EcEsi_GetLoadedCount`)
/// - [`EsiManager::get_files`] managed 缓存快照
pub struct EsiManager;

impl EsiManager {
    /// 默认 ESI 目录: <进程当前目录>/ESI
    pub fn default_path() -> String {
        if let Ok(cwd) = std::env::current_dir() {
            let dir = cwd.join("ESI");
            if !dir.exists() {
                let _ = std::fs::create_dir_all(&dir);
            }
            return dir.to_string_lossy().into_owned();
        }
        "./ESI".to_string()
    }

    /// 加载单个 ESI 文件 (managed + DLL 双缓存)
    ///
    /// 返回 DLL 报告的 Device 数; FFI 不可用时返回 0.
    pub fn add_file(file_path: &str) -> i32 {
        if file_path.is_empty() {
            return 0;
        }
        if let Ok(mut g) = ESI_MANAGER_FILES.lock() {
            ensure_files_init(&mut g).insert(file_path.to_string());
        }
        let cpath = match CString::new(file_path) {
            Ok(c) => c,
            Err(_) => return 0,
        };
        unsafe { crate::utils::ffi::EcEsi_LoadFile(cpath.as_ptr()) }
    }

    /// 加载目录下所有 .xml / .ESI 文件
    pub fn load_path(dir_path: &str) -> i32 {
        if dir_path.is_empty() {
            return 0;
        }
        if let Ok(entries) = std::fs::read_dir(dir_path) {
            if let Ok(mut g) = ESI_MANAGER_FILES.lock() {
                let set = ensure_files_init(&mut g);
                for e in entries.flatten() {
                    let path = e.path();
                    let name_lc = path
                        .file_name()
                        .and_then(|n| n.to_str())
                        .unwrap_or("")
                        .to_lowercase();
                    if name_lc.ends_with(".xml") || name_lc.ends_with(".esi") {
                        set.insert(path.to_string_lossy().into_owned());
                    }
                }
            }
        }
        let cpath = match CString::new(dir_path) {
            Ok(c) => c,
            Err(_) => return 0,
        };
        unsafe { crate::utils::ffi::EcEsi_LoadDirectory(cpath.as_ptr()) }
    }

    /// ESI 版本号匹配
    pub fn match_revision(actual: u32, expected: u32, strategy: EsiRevisionCheckStrategy) -> bool {
        match_revision(actual, expected, strategy)
    }

    /// 给单个从站绑定 ESI 文件 (对齐 EcEsi_BindToSlave)
    ///
    /// `slave_index` 1-based.
    pub fn bind_to_slave(master_index: u16, slave_index: u16, file_path: &str) -> i32 {
        let cpath = match CString::new(file_path) {
            Ok(c) => c,
            Err(_) => return 0,
        };
        unsafe {
            crate::utils::ffi::EcEsi_BindToSlave(master_index, slave_index, cpath.as_ptr())
        }
    }

    /// 自动给主站下所有从站匹配并应用 ESI (对齐 EcEsi_ApplyAllSlaves)
    pub fn apply_all_slaves(master_index: u16) -> i32 {
        unsafe { crate::utils::ffi::EcEsi_ApplyAllSlaves(master_index) }
    }

    /// 已加载文件数 (优先 DLL 报告值)
    pub fn get_loaded_count() -> i32 {
        let dll_n = unsafe { crate::utils::ffi::EcEsi_GetLoadedCount() };
        if dll_n > 0 {
            return dll_n;
        }
        if let Ok(g) = ESI_MANAGER_FILES.lock() {
            return g.as_ref().map(|s| s.len() as i32).unwrap_or(0);
        }
        0
    }

    /// 获取已加载的文件路径快照 (managed 缓存)
    pub fn get_files() -> Vec<String> {
        if let Ok(g) = ESI_MANAGER_FILES.lock() {
            if let Some(set) = g.as_ref() {
                return set.iter().cloned().collect();
            }
        }
        Vec::new()
    }

    /// 清空 managed + DLL 缓存
    pub fn clear() {
        if let Ok(mut g) = ESI_MANAGER_FILES.lock() {
            if let Some(set) = g.as_mut() {
                set.clear();
            }
        }
        unsafe { crate::utils::ffi::EcEsi_Clear() }
    }
}

// ============================================================================
// ENI (EtherCAT Network Information) 加载/保存包装 (ETG.2100)
// ============================================================================

/// 加载 ENI XML 配置
///
/// 内部调用 [`crate::utils::xml::load_xml_configuration`], 该函数已正确处理
/// `<EtherCATConfig>` 根 (ENI) 与 `<DarraEtherCATConfiguration>` 根 (DENI 旧版).
///
/// 对应 C# `Xml.cs::LoadENIConfiguration`.
pub fn load_eni(path: &str) -> Result<crate::utils::xml::MasterXmlConfiguration, String> {
    crate::utils::xml::load_xml_configuration(path)
}

/// 保存 ENI XML 配置 (managed 实装)
///
/// 用 `quick-xml` 直接生成 ETG.2100 兼容的 ENI XML, 与 `load_eni` 形成对称.
/// 与 C# `ESI.SaveENI` / Java `Eni.saveENI` 的 managed 路径对齐 — 不依赖
/// DLL 导出 `EcEni_Save`, 全部从 `MasterXmlConfiguration` 派生:
/// - `<EtherCATConfig>/<Config>/<Master>` 主站 Info + DENI 扩展 (CycleTime,
///   DcCycleTime, ExpectedSlaveCount)
/// - `<Slave>` 列表派生 Info (Name/PhysAddr/AutoIncAddr/AliasAddr/VendorId/
///   ProductCode/RevisionNo) + ProcessData SM 段 + DC AssignActivate
///
/// 重新加载: `load_eni` 已支持读回该格式.
pub fn save_eni(path: &str,
                config: &crate::utils::xml::MasterXmlConfiguration) -> Result<(), String> {
    use quick_xml::writer::Writer;
    use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event};
    use std::io::Cursor;

    if path.trim().is_empty() {
        return Err("save_eni: path 为空".to_string());
    }

    // 父目录确保存在
    if let Some(parent) = std::path::Path::new(path).parent() {
        if !parent.as_os_str().is_empty() && !parent.exists() {
            std::fs::create_dir_all(parent)
                .map_err(|e| format!("save_eni: 创建父目录失败: {}", e))?;
        }
    }

    let mut buf: Vec<u8> = Vec::new();
    {
        let cursor = Cursor::new(&mut buf);
        let mut w = Writer::new_with_indent(cursor, b' ', 2);

        // <?xml version="1.0" encoding="UTF-8"?>
        w.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))
            .map_err(|e| format!("save_eni: 写 XML 声明失败: {}", e))?;

        // 内联辅助: 写 <tag>text</tag>
        fn write_text<W: std::io::Write>(
            w: &mut Writer<W>, tag: &str, text: &str,
        ) -> Result<(), String> {
            w.write_event(Event::Start(BytesStart::new(tag)))
                .map_err(|e| format!("save_eni: 写 <{}> 失败: {}", tag, e))?;
            w.write_event(Event::Text(BytesText::new(text)))
                .map_err(|e| format!("save_eni: 写 <{}> 文本失败: {}", tag, e))?;
            w.write_event(Event::End(BytesEnd::new(tag)))
                .map_err(|e| format!("save_eni: 写 </{}> 失败: {}", tag, e))?;
            Ok(())
        }

        // <EtherCATConfig Version="1.0" xmlns:deni="...">
        let mut root = BytesStart::new("EtherCATConfig");
        root.push_attribute(("Version", "1.0"));
        root.push_attribute(("xmlns:deni", "http://www.darra-tech.com/schemas/eni/v3.0"));
        w.write_event(Event::Start(root))
            .map_err(|e| format!("save_eni: 写 <EtherCATConfig> 失败: {}", e))?;

        w.write_event(Event::Start(BytesStart::new("Config")))
            .map_err(|e| format!("save_eni: 写 <Config> 失败: {}", e))?;

        // ===== <Master> =====
        w.write_event(Event::Start(BytesStart::new("Master")))
            .map_err(|e| format!("save_eni: 写 <Master> 失败: {}", e))?;
        // <Info>
        w.write_event(Event::Start(BytesStart::new("Info")))
            .map_err(|e| format!("save_eni: 写 <Info> 失败: {}", e))?;
        write_text(&mut w, "Name", "DarraEtherCAT Master")?;
        write_text(&mut w, "Source", "DarraEtherCAT Rust SDK SaveENI")?;
        write_text(&mut w, "VendorId", "#x00001164")?;
        write_text(&mut w, "ProductCode", "#x00000001")?;
        w.write_event(Event::End(BytesEnd::new("Info")))
            .map_err(|e| format!("save_eni: 写 </Info> 失败: {}", e))?;
        // <CycleTime> / <DcCycleTime> (LoadENIConfiguration 兼容字段)
        write_text(&mut w, "CycleTime", &config.cycle_time_us.to_string())?;
        write_text(&mut w, "DcCycleTime", &config.dc_cycle_ns.to_string())?;
        // DENI 扩展节
        let mut deni_master = BytesStart::new("deni:MasterConfig");
        deni_master.push_attribute(("xmlns:deni", "http://www.darra-tech.com/schemas/eni/v3.0"));
        w.write_event(Event::Start(deni_master))
            .map_err(|e| format!("save_eni: 写 <deni:MasterConfig> 失败: {}", e))?;
        {
            let mut ct = BytesStart::new("deni:CycleTime");
            ct.push_attribute(("unit", "us"));
            w.write_event(Event::Start(ct))
                .map_err(|e| format!("save_eni: 写 deni:CycleTime 失败: {}", e))?;
            w.write_event(Event::Text(BytesText::new(&config.cycle_time_us.to_string())))
                .map_err(|e| format!("save_eni: 写 deni:CycleTime 文本失败: {}", e))?;
            w.write_event(Event::End(BytesEnd::new("deni:CycleTime")))
                .map_err(|e| format!("save_eni: 写 </deni:CycleTime> 失败: {}", e))?;
            let mut dct = BytesStart::new("deni:DcCycleTime");
            dct.push_attribute(("unit", "ns"));
            w.write_event(Event::Start(dct))
                .map_err(|e| format!("save_eni: 写 deni:DcCycleTime 失败: {}", e))?;
            w.write_event(Event::Text(BytesText::new(&config.dc_cycle_ns.to_string())))
                .map_err(|e| format!("save_eni: 写 deni:DcCycleTime 文本失败: {}", e))?;
            w.write_event(Event::End(BytesEnd::new("deni:DcCycleTime")))
                .map_err(|e| format!("save_eni: 写 </deni:DcCycleTime> 失败: {}", e))?;
            write_text(&mut w, "deni:ExpectedSlaveCount",
                       &config.expected_slave_count.to_string())?;
        }
        w.write_event(Event::End(BytesEnd::new("deni:MasterConfig")))
            .map_err(|e| format!("save_eni: 写 </deni:MasterConfig> 失败: {}", e))?;
        w.write_event(Event::End(BytesEnd::new("Master")))
            .map_err(|e| format!("save_eni: 写 </Master> 失败: {}", e))?;

        // ===== <Slave>... =====
        let mut auto_inc_counter: i32 = 0;
        for sl in &config.slaves {
            w.write_event(Event::Start(BytesStart::new("Slave")))
                .map_err(|e| format!("save_eni: 写 <Slave> 失败: {}", e))?;
            // <Info>
            w.write_event(Event::Start(BytesStart::new("Info")))
                .map_err(|e| format!("save_eni: 写 <Slave>/<Info> 失败: {}", e))?;
            write_text(&mut w, "Name", &sl.name)?;
            write_text(&mut w, "PhysAddr", &sl.config_addr.to_string())?;
            // ETG.2100 AutoIncAddr 习惯负向编号 (0, -1, -2 ...)
            write_text(&mut w, "AutoIncAddr", &(-auto_inc_counter).to_string())?;
            auto_inc_counter += 1;
            write_text(&mut w, "AliasAddr", &sl.alias_addr.to_string())?;
            write_text(&mut w, "VendorId", &format!("#x{:08X}", sl.vendor_id))?;
            write_text(&mut w, "ProductCode", &format!("#x{:08X}", sl.product_code))?;
            write_text(&mut w, "RevisionNo", &format!("#x{:08X}", sl.revision))?;
            w.write_event(Event::End(BytesEnd::new("Info")))
                .map_err(|e| format!("save_eni: 写 </Info> 失败: {}", e))?;

            // <ProcessData> (SM2/SM3) — 输入/输出长度
            if sl.input_size > 0 || sl.output_size > 0 {
                w.write_event(Event::Start(BytesStart::new("ProcessData")))
                    .map_err(|e| format!("save_eni: 写 <ProcessData> 失败: {}", e))?;
                if sl.output_size > 0 {
                    let mut sm = BytesStart::new("Sm");
                    sm.push_attribute(("No", "2"));
                    w.write_event(Event::Start(sm))
                        .map_err(|e| format!("save_eni: 写 <Sm No=2> 失败: {}", e))?;
                    write_text(&mut w, "DefaultSize", &sl.output_size.to_string())?;
                    write_text(&mut w, "StartAddress", "0")?;
                    w.write_event(Event::End(BytesEnd::new("Sm")))
                        .map_err(|e| format!("save_eni: 写 </Sm> 失败: {}", e))?;
                }
                if sl.input_size > 0 {
                    let mut sm = BytesStart::new("Sm");
                    sm.push_attribute(("No", "3"));
                    w.write_event(Event::Start(sm))
                        .map_err(|e| format!("save_eni: 写 <Sm No=3> 失败: {}", e))?;
                    write_text(&mut w, "DefaultSize", &sl.input_size.to_string())?;
                    write_text(&mut w, "StartAddress", "0")?;
                    w.write_event(Event::End(BytesEnd::new("Sm")))
                        .map_err(|e| format!("save_eni: 写 </Sm> 失败: {}", e))?;
                }
                w.write_event(Event::End(BytesEnd::new("ProcessData")))
                    .map_err(|e| format!("save_eni: 写 </ProcessData> 失败: {}", e))?;
            }

            // <Dc><AssignActivate>...</AssignActivate></Dc>
            // 从 slave_dc_configs 中查找匹配的 DC 配置
            let dc_match = config.slave_dc_configs.iter()
                .find(|c| c.slave_index as i32 == sl.index);
            if let Some(dc) = dc_match {
                if dc.sync0_cycle_ns != 0 || dc.sync1_cycle_ns != 0 {
                    w.write_event(Event::Start(BytesStart::new("Dc")))
                        .map_err(|e| format!("save_eni: 写 <Dc> 失败: {}", e))?;
                    // ETG.2100: AssignActivate 0x0300 = SYNC0+SYNC1, 0x0100 = 仅 SYNC0
                    let aa: u16 = if dc.sync1_cycle_ns != 0 { 0x0300 } else { 0x0100 };
                    write_text(&mut w, "AssignActivate", &format!("#x{:04X}", aa))?;
                    write_text(&mut w, "CycleTimeSync0", &dc.sync0_cycle_ns.to_string())?;
                    write_text(&mut w, "CycleTimeSync1", &dc.sync1_cycle_ns.to_string())?;
                    write_text(&mut w, "ShiftTimeSync0", &dc.shift_ns.to_string())?;
                    w.write_event(Event::End(BytesEnd::new("Dc")))
                        .map_err(|e| format!("save_eni: 写 </Dc> 失败: {}", e))?;
                }
            }

            w.write_event(Event::End(BytesEnd::new("Slave")))
                .map_err(|e| format!("save_eni: 写 </Slave> 失败: {}", e))?;
        }

        w.write_event(Event::End(BytesEnd::new("Config")))
            .map_err(|e| format!("save_eni: 写 </Config> 失败: {}", e))?;
        w.write_event(Event::End(BytesEnd::new("EtherCATConfig")))
            .map_err(|e| format!("save_eni: 写 </EtherCATConfig> 失败: {}", e))?;
    }

    std::fs::write(path, &buf)
        .map_err(|e| format!("save_eni: 写文件 {} 失败: {}", path, e))?;
    Ok(())
}
