//! Office 主题 `<a:theme>` 标准版。
//!
//! PowerPoint 强制要求一份合法的 theme1.xml，否则会被 Office/WPS 拒绝打开。
//! 这里直接采用 python-pptx 默认输出的 Office 主题，保留完整结构与所有字体脚本。
//! 出于二进制大小考虑，省略了 latin/ea/cs 之外的 `<a:font>` 列表（实际 Office 主题里全是它们）。
//!
//! # 与 python-pptx 的对应
//!
//! - `pptx.oxml.theme.Theme` ←→ [`Theme`] 结构体（v0.2 起支持结构化解析）；
//! - `pptx.parts.theme.ThemePart._element` 在加载时由 `parse_theme` 解析为 [`Theme`]；
//! - 写路径使用 [`Theme::to_xml`]（结构化序列化）或 [`default_theme_xml`]（完整 Office 主题 XML）。
//!
//! # 字体脚本表
//!
//! `THEME_XML` 完整列出了 30+ 个 script（`Jpan` / `Hang` / `Hans` / `Hant` / ...），
//! 缺失任何一个都会导致 PowerPoint 警告"主题不完整"。

use crate::oxml::ns::NS_DRAWING_MAIN;
use crate::oxml::writer::XmlWriter;

#[allow(dead_code)]
const THEME_NAME: &str = "Office Theme";

/// 结构化主题模型（对应 `<a:theme>`）。
///
/// 解析 `name` / `color_scheme` / `font_scheme` / `format_scheme` 四个核心字段。
/// `<a:objectDefaults>` / `<a:extraClrSchemeLst>` 等暂不解析（写路径用 [`default_theme_xml`]）。
#[derive(Clone, Debug, Default)]
pub struct Theme {
    /// 主题名（`<a:theme name="Office Theme">`）。
    pub name: String,
    /// 颜色方案（`<a:clrScheme>`）。
    pub color_scheme: ColorScheme,
    /// 字体方案（`<a:fontScheme>`）。
    pub font_scheme: FontScheme,
    /// 格式方案（`<a:fmtScheme>`）。
    pub format_scheme: FormatScheme,
    /// OPC part 路径（`/ppt/theme/themeN.xml`），用于 round-trip 保真。
    ///
    /// 新建场景为空字符串；`from_opc` 解析时回填，写路径优先使用此字段。
    pub partname: String,
}

/// 颜色方案（对应 `<a:clrScheme>`）。
///
/// 包含 12 个颜色槽位：dk1/lt1/dk2/lt2/accent1-6/hlink/folHlink。
#[derive(Clone, Debug, Default)]
pub struct ColorScheme {
    /// 颜色方案名（`<a:clrScheme name="Office">`）。
    pub name: String,
    /// 暗色 1（通常为黑色/文字色）。
    pub dk1: Option<ThemeColor>,
    /// 亮色 1（通常为白色/背景色）。
    pub lt1: Option<ThemeColor>,
    /// 暗色 2。
    pub dk2: Option<ThemeColor>,
    /// 亮色 2。
    pub lt2: Option<ThemeColor>,
    /// 强调色 1-6。
    pub accent1: Option<ThemeColor>,
    pub accent2: Option<ThemeColor>,
    pub accent3: Option<ThemeColor>,
    pub accent4: Option<ThemeColor>,
    pub accent5: Option<ThemeColor>,
    pub accent6: Option<ThemeColor>,
    /// 超链接颜色。
    pub hlink: Option<ThemeColor>,
    /// 已访问超链接颜色。
    pub fol_hlink: Option<ThemeColor>,
}

/// 字体方案（对应 `<a:fontScheme>`）。
///
/// 解析 majorFont / minorFont 的 latin / ea / cs typeface。
#[derive(Clone, Debug, Default)]
pub struct FontScheme {
    /// 字体方案名（`<a:fontScheme name="Office">`）。
    pub name: String,
    /// 主标题字体 latin（`<a:majorFont><a:latin typeface="..."/>`）。
    pub major_latin: String,
    /// 主标题字体 东亚（`<a:majorFont><a:ea typeface="..."/>`）。
    pub major_ea: String,
    /// 主标题字体 复杂文种（`<a:majorFont><a:cs typeface="..."/>`）。
    pub major_cs: String,
    /// 正文字体 latin（`<a:minorFont><a:latin typeface="..."/>`）。
    pub minor_latin: String,
    /// 正文字体 东亚（`<a:minorFont><a:ea typeface="..."/>`）。
    pub minor_ea: String,
    /// 正文字体 复杂文种（`<a:minorFont><a:cs typeface="..."/>`）。
    pub minor_cs: String,
}

/// 格式方案（对应 `<a:fmtScheme>`）。
///
/// FormatScheme 包含 fillStyleLst / lnStyleLst / effectStyleLst / bgFillStyleLst，
/// 每个都含有复杂的嵌套元素（gradFill / ln / effectLst / scene3d / sp3d 等）。
///
/// # 结构化策略
///
/// 采用**适度结构化**：保留 raw_xml 用于 round-trip，同时把 4 个 style 列表
/// 拆分为 `fill_styles` / `line_styles` / `effect_styles` / `bg_fill_styles`，
/// 每个元素是对应子元素的原始 XML 字符串（如 `<a:gradFill>...</a:gradFill>`）。
/// 这样用户可以查询/替换某个 style，而无需完整解析每个 fill/ln/effect 的内部结构。
#[derive(Clone, Debug, Default)]
pub struct FormatScheme {
    /// 格式方案名（`<a:fmtScheme name="Office">`）。
    pub name: String,
    /// 原始 XML 内容（`<a:fmtScheme>` 内部的子元素 XML）。
    ///
    /// 为空时 [`Theme::to_xml`] 会使用默认的 Office 格式方案。
    pub raw_xml: String,
    /// 填充样式列表（`<a:fillStyleLst>` 内的每个 fill 元素 XML）。
    ///
    /// 通常 3 个：solidFill / gradFill / gradFill。
    pub fill_styles: Vec<String>,
    /// 线条样式列表（`<a:lnStyleLst>` 内的每个 `<a:ln>` 元素 XML）。
    ///
    /// 通常 3 个。
    pub line_styles: Vec<String>,
    /// 效果样式列表（`<a:effectStyleLst>` 内的每个 `<a:effectStyle>` 元素 XML）。
    ///
    /// 通常 3 个。
    pub effect_styles: Vec<String>,
    /// 背景填充样式列表（`<a:bgFillStyleLst>` 内的每个 fill 元素 XML）。
    ///
    /// 通常 2 个。
    pub bg_fill_styles: Vec<String>,
}

/// 主题颜色值（对应 `<a:srgbClr>` 或 `<a:sysClr>`）。
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum ThemeColor {
    /// sRGB 颜色（`<a:srgbClr val="RRGGBB"/>`）。
    Srgb(String),
    /// 系统颜色（`<a:sysClr val="windowText" lastClr="000000"/>`）。
    Sys(String, String),
    /// 无颜色（解析失败或空槽位）。
    #[default]
    None,
}

impl ThemeColor {
    /// 写出对应的 XML 元素到 writer。
    ///
    /// # 元素结构
    ///
    /// - `Srgb` → `<a:srgbClr val="RRGGBB"/>`
    /// - `Sys` → `<a:sysClr val="..." lastClr="..."/>`
    /// - `None` → 不写出任何元素
    pub fn write_xml(&self, w: &mut XmlWriter) {
        match self {
            ThemeColor::Srgb(val) => {
                w.empty_with("a:srgbClr", &[("val", val.as_str())]);
            }
            ThemeColor::Sys(val, last_clr) => {
                w.empty_with(
                    "a:sysClr",
                    &[("val", val.as_str()), ("lastClr", last_clr.as_str())],
                );
            }
            ThemeColor::None => { /* 不写出 */ }
        }
    }
}

impl ColorScheme {
    /// 写出 `<a:clrScheme>` 元素到 writer。
    ///
    /// # 元素顺序（OOXML 规范）
    ///
    /// dk1 → lt1 → dk2 → lt2 → accent1 → accent2 → accent3 → accent4 →
    /// accent5 → accent6 → hlink → folHlink
    pub fn write_xml(&self, w: &mut XmlWriter) {
        let name = if self.name.is_empty() {
            "Office"
        } else {
            self.name.as_str()
        };
        w.open_with("a:clrScheme", &[("name", name)]);
        // 按 OOXML 规范顺序写出 12 个颜色槽位
        self.write_slot(w, "a:dk1", &self.dk1);
        self.write_slot(w, "a:lt1", &self.lt1);
        self.write_slot(w, "a:dk2", &self.dk2);
        self.write_slot(w, "a:lt2", &self.lt2);
        self.write_slot(w, "a:accent1", &self.accent1);
        self.write_slot(w, "a:accent2", &self.accent2);
        self.write_slot(w, "a:accent3", &self.accent3);
        self.write_slot(w, "a:accent4", &self.accent4);
        self.write_slot(w, "a:accent5", &self.accent5);
        self.write_slot(w, "a:accent6", &self.accent6);
        self.write_slot(w, "a:hlink", &self.hlink);
        self.write_slot(w, "a:folHlink", &self.fol_hlink);
        w.close("a:clrScheme");
    }

    /// 写出单个颜色槽位（如 `<a:dk1><a:srgbClr val="..."/></a:dk1>`）。
    fn write_slot(&self, w: &mut XmlWriter, tag: &str, color: &Option<ThemeColor>) {
        w.open(tag);
        if let Some(c) = color {
            c.write_xml(w);
        }
        w.close(tag);
    }
}

impl FontScheme {
    /// 写出 `<a:fontScheme>` 元素到 writer。
    ///
    /// # 元素结构
    ///
    /// ```text
    /// <a:fontScheme name="...">
    ///   <a:majorFont>
    ///     <a:latin typeface="..."/>
    ///     <a:ea typeface="..."/>
    ///     <a:cs typeface="..."/>
    ///   </a:majorFont>
    ///   <a:minorFont>
    ///     <a:latin typeface="..."/>
    ///     <a:ea typeface="..."/>
    ///     <a:cs typeface="..."/>
    ///   </a:minorFont>
    /// </a:fontScheme>
    /// ```
    pub fn write_xml(&self, w: &mut XmlWriter) {
        let name = if self.name.is_empty() {
            "Office"
        } else {
            self.name.as_str()
        };
        w.open_with("a:fontScheme", &[("name", name)]);
        // majorFont
        w.open("a:majorFont");
        w.empty_with("a:latin", &[("typeface", self.major_latin.as_str())]);
        w.empty_with("a:ea", &[("typeface", self.major_ea.as_str())]);
        w.empty_with("a:cs", &[("typeface", self.major_cs.as_str())]);
        w.close("a:majorFont");
        // minorFont
        w.open("a:minorFont");
        w.empty_with("a:latin", &[("typeface", self.minor_latin.as_str())]);
        w.empty_with("a:ea", &[("typeface", self.minor_ea.as_str())]);
        w.empty_with("a:cs", &[("typeface", self.minor_cs.as_str())]);
        w.close("a:minorFont");
        w.close("a:fontScheme");
    }
}

impl FormatScheme {
    /// 写出 `<a:fmtScheme>` 元素到 writer。
    ///
    /// 优先级：结构化字段（fill_styles 等）> raw_xml > 默认 Office 格式方案。
    /// 若结构化字段非空，按 OOXML 顺序输出 4 个 style 列表；否则回退到 raw_xml。
    pub fn write_xml(&self, w: &mut XmlWriter) {
        let name = if self.name.is_empty() {
            "Office"
        } else {
            self.name.as_str()
        };
        // 判断是否使用结构化字段
        let has_structured = !self.fill_styles.is_empty()
            || !self.line_styles.is_empty()
            || !self.effect_styles.is_empty()
            || !self.bg_fill_styles.is_empty();

        if has_structured {
            // 使用结构化字段按 OOXML 顺序输出
            w.open_with("a:fmtScheme", &[("name", name)]);
            if !self.fill_styles.is_empty() {
                w.open("a:fillStyleLst");
                for s in &self.fill_styles {
                    w.raw(s);
                }
                w.close("a:fillStyleLst");
            }
            if !self.line_styles.is_empty() {
                w.open("a:lnStyleLst");
                for s in &self.line_styles {
                    w.raw(s);
                }
                w.close("a:lnStyleLst");
            }
            if !self.effect_styles.is_empty() {
                w.open("a:effectStyleLst");
                for s in &self.effect_styles {
                    w.raw(s);
                }
                w.close("a:effectStyleLst");
            }
            if !self.bg_fill_styles.is_empty() {
                w.open("a:bgFillStyleLst");
                for s in &self.bg_fill_styles {
                    w.raw(s);
                }
                w.close("a:bgFillStyleLst");
            }
            w.close("a:fmtScheme");
        } else if self.raw_xml.is_empty() {
            // 使用默认的 Office 格式方案（从 THEME_XML 中提取）
            w.raw(DEFAULT_FMT_SCHEME);
        } else {
            // 回退到 raw_xml
            w.open_with("a:fmtScheme", &[("name", name)]);
            w.raw(&self.raw_xml);
            w.close("a:fmtScheme");
        }
    }

    /// 从 raw_xml 解析填充 4 个结构化字段（fill_styles / line_styles / effect_styles / bg_fill_styles）。
    ///
    /// 解析后结构化字段非空，后续 [`write_xml`](Self::write_xml) 会优先使用结构化字段。
    /// 若 raw_xml 为空，此方法不做任何操作。
    ///
    /// # 解析策略
    ///
    /// 使用简单字符串查找定位 4 个 `<a:xxxStyleLst>` 容器，然后收集每个容器的
    /// 直接子元素 XML。不解析子元素的内部结构（如 gradFill 的 gsLst），保留原始 XML。
    pub fn parse_from_raw_xml(&mut self) {
        if self.raw_xml.is_empty() {
            return;
        }
        self.fill_styles = collect_style_lst_children(&self.raw_xml, "fillStyleLst");
        self.line_styles = collect_style_lst_children(&self.raw_xml, "lnStyleLst");
        self.effect_styles = collect_style_lst_children(&self.raw_xml, "effectStyleLst");
        self.bg_fill_styles = collect_style_lst_children(&self.raw_xml, "bgFillStyleLst");
    }

    /// 填充样式数量。
    pub fn fill_style_count(&self) -> usize {
        self.fill_styles.len()
    }

    /// 线条样式数量。
    pub fn line_style_count(&self) -> usize {
        self.line_styles.len()
    }

    /// 效果样式数量。
    pub fn effect_style_count(&self) -> usize {
        self.effect_styles.len()
    }

    /// 背景填充样式数量。
    pub fn bg_fill_style_count(&self) -> usize {
        self.bg_fill_styles.len()
    }
}

/// 从 XML 字符串中收集指定 `<a:xxxLst>` 容器的直接子元素 XML。
///
/// 例如 `collect_style_lst_children(xml, "fillStyleLst")` 会找到 `<a:fillStyleLst>...</a:fillStyleLst>`，
/// 返回其内部每个直接子元素的完整 XML（如 `<a:solidFill>...</a:solidFill>`）。
///
/// # 算法
///
/// 使用简单的括号配对算法：在容器内遇到 Start 事件开始收集，遇到对应的 End 事件结束收集。
/// 自闭合 Empty 事件直接收集。不递归到子元素（只收集直接子元素）。
fn collect_style_lst_children(xml: &str, container_local: &str) -> Vec<String> {
    let mut result = Vec::new();
    let mut rd = quick_xml::Reader::from_str(xml);
    rd.config_mut().trim_text(true);
    let mut buf = Vec::new();

    // 状态机：先找容器，再收集子元素
    enum State {
        Seeking,
        InContainer,
    }
    let mut state = State::Seeking;

    loop {
        match rd.read_event_into(&mut buf) {
            Ok(quick_xml::events::Event::Start(e)) => {
                // 把 e.name() 绑定到 let，避免临时 QName 在 as_ref() 后 drop
                let name = e.name();
                let local = local_name_quick(name.as_ref());
                match state {
                    State::Seeking => {
                        if local == container_local.as_bytes() {
                            state = State::InContainer;
                        }
                    }
                    State::InContainer => {
                        // 收集完整子元素（含开闭标签）
                        if let Ok(inner) = collect_full_element_str(&mut rd, e.into_owned()) {
                            result.push(inner);
                        }
                    }
                }
            }
            Ok(quick_xml::events::Event::End(e)) => {
                let name = e.name();
                let local = local_name_quick(name.as_ref());
                if let State::InContainer = state {
                    if local == container_local.as_bytes() {
                        state = State::Seeking;
                    }
                }
            }
            Ok(quick_xml::events::Event::Empty(e)) => {
                let name = e.name();
                let local = local_name_quick(name.as_ref());
                if let State::InContainer = state {
                    // 自闭合子元素，重构为完整 XML 字符串
                    // 注意：用完整 name（含命名空间前缀）而非 local，保证 round-trip
                    let _ = local; // local 仅用于上面的状态判断保留
                    let mut s = String::new();
                    s.push('<');
                    s.push_str(std::str::from_utf8(name.as_ref()).unwrap_or(""));
                    for a in e.attributes().flatten() {
                        s.push(' ');
                        s.push_str(std::str::from_utf8(a.key.as_ref()).unwrap_or(""));
                        s.push_str("=\"");
                        s.push_str(
                            a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
                                .unwrap_or_default()
                                .as_ref(),
                        );
                        s.push('"');
                    }
                    s.push_str("/>");
                    result.push(s);
                }
            }
            Ok(quick_xml::events::Event::Eof) => break,
            Err(_) => break,
            _ => {}
        }
        buf.clear();
    }
    result
}

/// 取 XML 元素名的 local part（去掉命名空间前缀）。
fn local_name_quick(name: &[u8]) -> &[u8] {
    // 处理 "a:fillStyleLst" → "fillStyleLst"
    match name.iter().position(|&b| b == b':') {
        Some(pos) => &name[pos + 1..],
        None => name,
    }
}

/// 收集从当前 Start 事件到对应 End 事件的完整 XML 字符串（含开闭标签）。
///
/// 与 parse_sld.rs 中的 `collect_full_element` 功能相同，但返回 String 而非写入 buffer。
fn collect_full_element_str(
    rd: &mut quick_xml::Reader<&[u8]>,
    start: quick_xml::events::BytesStart<'_>,
) -> quick_xml::Result<String> {
    let mut result = String::new();
    // 写入开标签
    // 把 start.name() 绑定到 let，避免临时 QName 在 as_ref() 后 drop
    let start_name = start.name();
    let name = std::str::from_utf8(start_name.as_ref()).unwrap_or("");
    result.push('<');
    result.push_str(name);
    for a in start.attributes().flatten() {
        result.push(' ');
        result.push_str(std::str::from_utf8(a.key.as_ref()).unwrap_or(""));
        result.push_str("=\"");
        result.push_str(
            a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
                .unwrap_or_default()
                .as_ref(),
        );
        result.push('"');
    }
    result.push('>');

    let target = start.name();
    let mut depth = 1;
    let mut buf = Vec::new();
    loop {
        match rd.read_event_into(&mut buf) {
            Ok(quick_xml::events::Event::Start(e)) => {
                // 把 e.name() 绑定到 let，避免临时 QName 在 as_ref() 后 drop
                let e_name = e.name();
                let n = std::str::from_utf8(e_name.as_ref()).unwrap_or("");
                result.push('<');
                result.push_str(n);
                for a in e.attributes().flatten() {
                    result.push(' ');
                    result.push_str(std::str::from_utf8(a.key.as_ref()).unwrap_or(""));
                    result.push_str("=\"");
                    result.push_str(
                        a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
                            .unwrap_or_default()
                            .as_ref(),
                    );
                    result.push('"');
                }
                result.push('>');
                if e.name() == target {
                    depth += 1;
                }
            }
            Ok(quick_xml::events::Event::End(e)) => {
                let e_name = e.name();
                let n = std::str::from_utf8(e_name.as_ref()).unwrap_or("");
                result.push_str("</");
                result.push_str(n);
                result.push('>');
                if e.name() == target {
                    depth -= 1;
                    if depth == 0 {
                        break;
                    }
                }
            }
            Ok(quick_xml::events::Event::Empty(e)) => {
                let e_name = e.name();
                let n = std::str::from_utf8(e_name.as_ref()).unwrap_or("");
                result.push('<');
                result.push_str(n);
                for a in e.attributes().flatten() {
                    result.push(' ');
                    result.push_str(std::str::from_utf8(a.key.as_ref()).unwrap_or(""));
                    result.push_str("=\"");
                    result.push_str(
                        a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
                            .unwrap_or_default()
                            .as_ref(),
                    );
                    result.push('"');
                }
                result.push_str("/>");
            }
            Ok(quick_xml::events::Event::Text(t)) => {
                // quick-xml 0.40: BytesText::unescape() 方法已移除，
                // 改用 quick_xml::escape::unescape 函数（接受 &str）。
                // BytesText 的 Deref 目标是 [u8]，需要先转成 &str。
                let text_str = std::str::from_utf8(t.as_ref()).unwrap_or("");
                result.push_str(&quick_xml::escape::unescape(text_str).unwrap_or_default());
            }
            Ok(quick_xml::events::Event::Eof) => break,
            Err(e) => return Err(e),
            _ => {}
        }
        buf.clear();
    }
    Ok(result)
}

impl Theme {
    /// 从结构化模型序列化为完整 theme XML。
    ///
    /// 与 [`default_theme_xml`] 不同，此方法根据 [`Theme`] 结构体的字段值
    /// 生成 XML，允许用户修改颜色方案、字体方案等。
    ///
    /// # 注意
    ///
    /// - 字体脚本表（`<a:font script="..." typeface="..."/>`）不在此方法中生成，
    ///   因为它们不影响 PowerPoint 的核心解析。如需完整字体脚本，使用 [`default_theme_xml`]。
    /// - `FormatScheme` 若 `raw_xml` 为空，使用默认 Office 格式方案。
    pub fn to_xml(&self) -> String {
        let mut w = XmlWriter::with_decl();
        let theme_name = if self.name.is_empty() {
            "Office Theme"
        } else {
            self.name.as_str()
        };
        w.open_with(
            "a:theme",
            &[("xmlns:a", NS_DRAWING_MAIN), ("name", theme_name)],
        );
        // themeElements
        w.open("a:themeElements");
        self.color_scheme.write_xml(&mut w);
        self.font_scheme.write_xml(&mut w);
        self.format_scheme.write_xml(&mut w);
        w.close("a:themeElements");
        // objectDefaults（简化：空）
        w.empty("a:objectDefaults");
        // extraClrSchemeLst（简化：空）
        w.empty("a:extraClrSchemeLst");
        w.close("a:theme");
        w.into_string()
    }
}

/// 返回完整且经过验证的 Office 主题 XML（与 python-pptx 默认输出对齐）。
///
/// 这是写路径的默认实现，保证 PowerPoint/WPS 能正确打开。
/// 如需根据 [`Theme`] 结构体生成自定义 XML，使用 [`Theme::to_xml`]。
pub fn default_theme_xml() -> String {
    THEME_XML.to_string()
}

/// 默认的 `<a:fmtScheme>` 元素（从完整 Office 主题中提取）。
///
/// 包含 fillStyleLst / lnStyleLst / effectStyleLst / bgFillStyleLst。
const DEFAULT_FMT_SCHEME: &str = r#"<a:fmtScheme name="Office"><a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="50000"/><a:satMod val="300000"/></a:schemeClr></a:gs><a:gs pos="35000"><a:schemeClr val="phClr"><a:tint val="37000"/><a:satMod val="300000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:tint val="15000"/><a:satMod val="350000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="16200000" scaled="1"/></a:gradFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="100000"/><a:shade val="100000"/><a:satMod val="130000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:tint val="50000"/><a:shade val="100000"/><a:satMod val="350000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="16200000" scaled="0"/></a:gradFill></a:fillStyleLst><a:lnStyleLst><a:ln w="9525" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"><a:shade val="95000"/><a:satMod val="105000"/></a:schemeClr></a:solidFill><a:prstDash val="solid"/></a:ln><a:ln w="25400" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/></a:ln><a:ln w="38100" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst><a:outerShdw blurRad="40000" dist="20000" dir="5400000" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="38000"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle><a:effectStyle><a:effectLst><a:outerShdw blurRad="40000" dist="23000" dir="5400000" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="35000"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle><a:effectStyle><a:effectLst><a:outerShdw blurRad="40000" dist="23000" dir="5400000" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="35000"/></a:srgbClr></a:outerShdw></a:effectLst><a:scene3d><a:camera prst="orthographicFront"><a:rot lat="0" lon="0" rev="0"/></a:camera><a:lightRig rig="threePt" dir="t"><a:rot lat="0" lon="0" rev="1200000"/></a:lightRig></a:scene3d><a:sp3d><a:bevelT w="63500" h="25400"/></a:sp3d></a:effectStyle></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="40000"/><a:satMod val="350000"/></a:schemeClr></a:gs><a:gs pos="40000"><a:schemeClr val="phClr"><a:tint val="45000"/><a:shade val="99000"/><a:satMod val="350000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="20000"/><a:satMod val="255000"/></a:schemeClr></a:gs></a:gsLst><a:path path="circle"><a:fillToRect l="50000" t="-80000" r="50000" b="180000"/></a:path></a:gradFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="80000"/><a:satMod val="300000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="30000"/><a:satMod val="200000"/></a:schemeClr></a:gs></a:gsLst><a:path path="circle"><a:fillToRect l="50000" t="50000" r="50000" b="50000"/></a:path></a:gradFill></a:bgFillStyleLst></a:fmtScheme>"#;

/// 完整且经过验证的 Office 主题 XML（与 python-pptx 默认输出对齐）。
const THEME_XML: &str = r##"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme"><a:themeElements><a:clrScheme name="Office"><a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1><a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1><a:dk2><a:srgbClr val="1F497D"/></a:dk2><a:lt2><a:srgbClr val="EEECE1"/></a:lt2><a:accent1><a:srgbClr val="4F81BD"/></a:accent1><a:accent2><a:srgbClr val="C0504D"/></a:accent2><a:accent3><a:srgbClr val="9BBB59"/></a:accent3><a:accent4><a:srgbClr val="8064A2"/></a:accent4><a:accent5><a:srgbClr val="4BACC6"/></a:accent5><a:accent6><a:srgbClr val="F79646"/></a:accent6><a:hlink><a:srgbClr val="0000FF"/></a:hlink><a:folHlink><a:srgbClr val="800080"/></a:folHlink></a:clrScheme><a:fontScheme name="Office"><a:majorFont><a:latin typeface="Calibri"/><a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="ＭＳ Ｐゴシック"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="宋体"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Times New Roman"/><a:font script="Hebr" typeface="Times New Roman"/><a:font script="Thai" typeface="Angsana New"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="MoolBoran"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Times New Roman"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/></a:majorFont><a:minorFont><a:latin typeface="Calibri"/><a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="ＭＳ Ｐゴシック"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="宋体"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Arial"/><a:font script="Hebr" typeface="Arial"/><a:font script="Thai" typeface="Cordia New"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="DaunPenh"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Arial"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/></a:minorFont></a:fontScheme><a:fmtScheme name="Office"><a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="50000"/><a:satMod val="300000"/></a:schemeClr></a:gs><a:gs pos="35000"><a:schemeClr val="phClr"><a:tint val="37000"/><a:satMod val="300000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:tint val="15000"/><a:satMod val="350000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="16200000" scaled="1"/></a:gradFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="100000"/><a:shade val="100000"/><a:satMod val="130000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:tint val="50000"/><a:shade val="100000"/><a:satMod val="350000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="16200000" scaled="0"/></a:gradFill></a:fillStyleLst><a:lnStyleLst><a:ln w="9525" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"><a:shade val="95000"/><a:satMod val="105000"/></a:schemeClr></a:solidFill><a:prstDash val="solid"/></a:ln><a:ln w="25400" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/></a:ln><a:ln w="38100" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst><a:outerShdw blurRad="40000" dist="20000" dir="5400000" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="38000"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle><a:effectStyle><a:effectLst><a:outerShdw blurRad="40000" dist="23000" dir="5400000" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="35000"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle><a:effectStyle><a:effectLst><a:outerShdw blurRad="40000" dist="23000" dir="5400000" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="35000"/></a:srgbClr></a:outerShdw></a:effectLst><a:scene3d><a:camera prst="orthographicFront"><a:rot lat="0" lon="0" rev="0"/></a:camera><a:lightRig rig="threePt" dir="t"><a:rot lat="0" lon="0" rev="1200000"/></a:lightRig></a:scene3d><a:sp3d><a:bevelT w="63500" h="25400"/></a:sp3d></a:effectStyle></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="40000"/><a:satMod val="350000"/></a:schemeClr></a:gs><a:gs pos="40000"><a:schemeClr val="phClr"><a:tint val="45000"/><a:shade val="99000"/><a:satMod val="350000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="20000"/><a:satMod val="255000"/></a:schemeClr></a:gs></a:gsLst><a:path path="circle"><a:fillToRect l="50000" t="-80000" r="50000" b="180000"/></a:path></a:gradFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="80000"/><a:satMod val="300000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="30000"/><a:satMod val="200000"/></a:schemeClr></a:gs></a:gsLst><a:path path="circle"><a:fillToRect l="50000" t="50000" r="50000" b="50000"/></a:path></a:gradFill></a:bgFillStyleLst></a:fmtScheme></a:themeElements><a:objectDefaults><a:spDef><a:spPr/><a:bodyPr/><a:lstStyle/><a:style><a:lnRef idx="1"><a:schemeClr val="accent1"/></a:lnRef><a:fillRef idx="3"><a:schemeClr val="accent1"/></a:fillRef><a:effectRef idx="2"><a:schemeClr val="accent1"/></a:effectRef><a:fontRef idx="minor"><a:schemeClr val="lt1"/></a:fontRef></a:style></a:spDef><a:lnDef><a:spPr/><a:bodyPr/><a:lstStyle/><a:style><a:lnRef idx="2"><a:schemeClr val="accent1"/></a:lnRef><a:fillRef idx="0"><a:schemeClr val="accent1"/></a:fillRef><a:effectRef idx="1"><a:schemeClr val="accent1"/></a:effectRef><a:fontRef idx="minor"><a:schemeClr val="tx1"/></a:fontRef></a:style></a:lnDef></a:objectDefaults><a:extraClrSchemeLst/></a:theme>"##;

#[cfg(test)]
mod tests {
    use super::*;

    /// 验证 `Theme::to_xml` 能生成合法的 theme XML。
    #[test]
    fn theme_to_xml_generates_valid_xml() {
        let mut theme = Theme {
            name: "Test Theme".to_string(),
            ..Default::default()
        };
        theme.color_scheme.name = "Test".to_string();
        theme.color_scheme.dk1 = Some(ThemeColor::Srgb("000000".to_string()));
        theme.color_scheme.lt1 = Some(ThemeColor::Srgb("FFFFFF".to_string()));
        theme.font_scheme.major_latin = "Arial".to_string();
        theme.font_scheme.minor_latin = "Calibri".to_string();

        let xml = theme.to_xml();
        // 验证关键元素存在
        assert!(xml.contains(r#"name="Test Theme""#), "theme name missing");
        assert!(
            xml.contains(r#"<a:clrScheme name="Test">"#),
            "clrScheme missing"
        );
        assert!(
            xml.contains(r#"<a:dk1><a:srgbClr val="000000"/></a:dk1>"#),
            "dk1 missing"
        );
        assert!(
            xml.contains(r#"<a:lt1><a:srgbClr val="FFFFFF"/></a:lt1>"#),
            "lt1 missing"
        );
        assert!(
            xml.contains(r#"<a:latin typeface="Arial"/>"#),
            "major latin missing"
        );
        assert!(
            xml.contains(r#"<a:latin typeface="Calibri"/>"#),
            "minor latin missing"
        );
        // 验证 fmtScheme 存在（默认）
        assert!(xml.contains("<a:fmtScheme"), "fmtScheme missing");
    }

    /// 验证 `ThemeColor::write_xml` 对 Srgb 类型的序列化。
    #[test]
    fn theme_color_srgb_write_xml() {
        let mut w = XmlWriter::new();
        let color = ThemeColor::Srgb("FF0000".to_string());
        color.write_xml(&mut w);
        assert_eq!(w.buf, r#"<a:srgbClr val="FF0000"/>"#);
    }

    /// 验证 `ThemeColor::write_xml` 对 Sys 类型的序列化。
    #[test]
    fn theme_color_sys_write_xml() {
        let mut w = XmlWriter::new();
        let color = ThemeColor::Sys("windowText".to_string(), "000000".to_string());
        color.write_xml(&mut w);
        assert_eq!(w.buf, r#"<a:sysClr val="windowText" lastClr="000000"/>"#);
    }

    /// 验证 `ThemeColor::write_xml` 对 None 类型不输出任何内容。
    #[test]
    fn theme_color_none_write_xml() {
        let mut w = XmlWriter::new();
        let color = ThemeColor::None;
        color.write_xml(&mut w);
        assert_eq!(w.buf, "");
    }

    /// 验证 `ColorScheme::write_xml` 按 OOXML 顺序写出 12 个颜色槽位。
    #[test]
    fn color_scheme_write_xml_order() {
        let scheme = ColorScheme {
            name: "Test".to_string(),
            dk1: Some(ThemeColor::Srgb("000000".to_string())),
            lt1: Some(ThemeColor::Srgb("FFFFFF".to_string())),
            accent1: Some(ThemeColor::Srgb("4F81BD".to_string())),
            ..Default::default()
        };

        let mut w = XmlWriter::new();
        scheme.write_xml(&mut w);
        let xml = &w.buf;

        // 验证顺序：dk1 在 lt1 之前，lt1 在 accent1 之前
        let dk1_pos = xml.find("<a:dk1>").unwrap();
        let lt1_pos = xml.find("<a:lt1>").unwrap();
        let accent1_pos = xml.find("<a:accent1>").unwrap();
        assert!(dk1_pos < lt1_pos, "dk1 should come before lt1");
        assert!(lt1_pos < accent1_pos, "lt1 should come before accent1");
    }

    /// 验证 `FontScheme::write_xml` 写出 majorFont 和 minorFont。
    #[test]
    fn font_scheme_write_xml() {
        let fs = FontScheme {
            name: "Test".to_string(),
            major_latin: "Calibri Light".to_string(),
            major_ea: "宋体".to_string(),
            minor_latin: "Calibri".to_string(),
            minor_ea: "宋体".to_string(),
            ..Default::default()
        };

        let mut w = XmlWriter::new();
        fs.write_xml(&mut w);
        let xml = &w.buf;

        assert!(
            xml.contains(r#"<a:latin typeface="Calibri Light"/>"#),
            "major latin missing"
        );
        assert!(
            xml.contains(r#"<a:ea typeface="宋体"/>"#),
            "ea typeface missing"
        );
        assert!(
            xml.contains(r#"<a:latin typeface="Calibri"/>"#),
            "minor latin missing"
        );
    }

    /// 验证 `default_theme_xml` 返回非空且包含关键元素。
    #[test]
    fn default_theme_xml_is_valid() {
        let xml = default_theme_xml();
        assert!(!xml.is_empty());
        assert!(xml.contains("<a:theme"));
        assert!(xml.contains("<a:clrScheme"));
        assert!(xml.contains("<a:fontScheme"));
        assert!(xml.contains("<a:fmtScheme"));
    }

    // ===================== TODO-005：FormatScheme 结构化解析测试 =====================

    /// 构造一份简化的 fmtScheme raw_xml，用于测试结构化解析。
    ///
    /// 包含完整的 4 个 style 列表容器，每个容器有 1-2 个子元素。
    fn sample_fmt_scheme_raw_xml() -> &'static str {
        r#"<a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"/></a:gs></a:gsLst></a:gradFill></a:fillStyleLst><a:lnStyleLst><a:ln w="9525" cap="flat"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:ln><a:ln w="25400"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst><a:outerShdw blurRad="40000"/></a:effectLst></a:effectStyle></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:bgFillStyleLst>"#
    }

    /// 验证 `parse_from_raw_xml` 能正确拆分 4 个 style 列表。
    ///
    /// 每个列表的子元素数量与原始 XML 中一致：
    /// - fillStyleLst: 2 个（solidFill + gradFill）
    /// - lnStyleLst: 2 个（两个 ln）
    /// - effectStyleLst: 1 个（effectStyle）
    /// - bgFillStyleLst: 1 个（solidFill）
    #[test]
    fn fmt_scheme_parse_from_raw_xml_splits_four_lists() {
        let mut fmt = FormatScheme {
            name: "Test".to_string(),
            raw_xml: sample_fmt_scheme_raw_xml().to_string(),
            ..Default::default()
        };

        // 解析前为空
        assert_eq!(fmt.fill_style_count(), 0);
        assert_eq!(fmt.line_style_count(), 0);
        assert_eq!(fmt.effect_style_count(), 0);
        assert_eq!(fmt.bg_fill_style_count(), 0);

        fmt.parse_from_raw_xml();

        // 解析后正确拆分
        assert_eq!(fmt.fill_style_count(), 2, "fillStyleLst 应有 2 个子元素");
        assert_eq!(fmt.line_style_count(), 2, "lnStyleLst 应有 2 个子元素");
        assert_eq!(
            fmt.effect_style_count(),
            1,
            "effectStyleLst 应有 1 个子元素"
        );
        assert_eq!(
            fmt.bg_fill_style_count(),
            1,
            "bgFillStyleLst 应有 1 个子元素"
        );
    }

    /// 验证解析后的子元素 XML 内容正确（含完整开闭标签与属性）。
    #[test]
    fn fmt_scheme_parsed_children_content_correct() {
        let mut fmt = FormatScheme {
            raw_xml: sample_fmt_scheme_raw_xml().to_string(),
            ..Default::default()
        };
        fmt.parse_from_raw_xml();

        // 第一个 fill 子元素是 solidFill
        let fill0 = &fmt.fill_styles[0];
        assert!(
            fill0.starts_with("<a:solidFill>"),
            "fill[0] 应以 <a:solidFill> 开头"
        );
        assert!(
            fill0.contains(r#"<a:schemeClr val="phClr"/>"#),
            "fill[0] 应包含 schemeClr"
        );
        assert!(
            fill0.ends_with("</a:solidFill>"),
            "fill[0] 应以 </a:solidFill> 结尾"
        );

        // 第二个 fill 子元素是 gradFill（带属性 rotWithShape="1"）
        let fill1 = &fmt.fill_styles[1];
        assert!(
            fill1.starts_with(r#"<a:gradFill rotWithShape="1">"#),
            "fill[1] 应包含属性"
        );

        // 第一个 ln 子元素带 w/cap 属性
        let ln0 = &fmt.line_styles[0];
        assert!(
            ln0.starts_with(r#"<a:ln w="9525" cap="flat">"#),
            "ln[0] 应带属性"
        );

        // effectStyle 子元素嵌套 outerShdw
        let eff0 = &fmt.effect_styles[0];
        assert!(eff0.contains("<a:outerShdw"), "effect[0] 应包含 outerShdw");
    }

    /// 验证结构化 `write_xml` 输出正确的 `<a:fillStyleLst>` 等容器。
    #[test]
    fn fmt_scheme_structured_write_xml_outputs_containers() {
        let mut fmt = FormatScheme {
            name: "Office".to_string(),
            raw_xml: sample_fmt_scheme_raw_xml().to_string(),
            ..Default::default()
        };
        fmt.parse_from_raw_xml();

        let mut w = XmlWriter::new();
        fmt.write_xml(&mut w);
        let xml = &w.buf;

        // 验证 4 个容器都存在且按 OOXML 顺序
        let fill_pos = xml.find("<a:fillStyleLst>").expect("fillStyleLst missing");
        let ln_pos = xml.find("<a:lnStyleLst>").expect("lnStyleLst missing");
        let eff_pos = xml
            .find("<a:effectStyleLst>")
            .expect("effectStyleLst missing");
        let bg_pos = xml
            .find("<a:bgFillStyleLst>")
            .expect("bgFillStyleLst missing");

        // 顺序：fillStyleLst → lnStyleLst → effectStyleLst → bgFillStyleLst
        assert!(fill_pos < ln_pos, "fillStyleLst 应在 lnStyleLst 之前");
        assert!(ln_pos < eff_pos, "lnStyleLst 应在 effectStyleLst 之前");
        assert!(eff_pos < bg_pos, "effectStyleLst 应在 bgFillStyleLst 之前");

        // 验证根元素带 name 属性
        assert!(
            xml.contains(r#"<a:fmtScheme name="Office">"#),
            "fmtScheme 应带 name 属性"
        );

        // 验证容器内有子元素内容（不是空容器）
        assert!(xml.contains("<a:solidFill>"), "应包含 solidFill 子元素");
        assert!(xml.contains("<a:ln "), "应包含 ln 子元素");
    }

    /// 验证查询方法（fill_style_count 等）在解析后返回正确数量。
    #[test]
    fn fmt_scheme_count_methods_after_parse() {
        let mut fmt = FormatScheme {
            raw_xml: sample_fmt_scheme_raw_xml().to_string(),
            ..Default::default()
        };
        fmt.parse_from_raw_xml();

        assert_eq!(fmt.fill_style_count(), 2);
        assert_eq!(fmt.line_style_count(), 2);
        assert_eq!(fmt.effect_style_count(), 1);
        assert_eq!(fmt.bg_fill_style_count(), 1);
    }

    /// 验证 `parse_from_raw_xml` 在 raw_xml 为空时不做任何操作。
    #[test]
    fn fmt_scheme_parse_empty_raw_xml_is_noop() {
        let mut fmt = FormatScheme::default();
        // raw_xml 默认为空
        fmt.parse_from_raw_xml();

        assert_eq!(fmt.fill_style_count(), 0);
        assert_eq!(fmt.line_style_count(), 0);
        assert_eq!(fmt.effect_style_count(), 0);
        assert_eq!(fmt.bg_fill_style_count(), 0);
    }

    /// 验证 `write_xml` 在结构化字段为空但 raw_xml 非空时回退到 raw_xml。
    #[test]
    fn fmt_scheme_write_xml_falls_back_to_raw_xml() {
        let fmt = FormatScheme {
            name: "Office".to_string(),
            raw_xml: r#"<a:customChild/>"#.to_string(),
            ..Default::default()
        };
        // 不调用 parse_from_raw_xml，结构化字段保持为空

        let mut w = XmlWriter::new();
        fmt.write_xml(&mut w);
        let xml = &w.buf;

        // 应输出 <a:fmtScheme name="Office"> + raw_xml + </a:fmtScheme>
        assert!(
            xml.contains(r#"<a:fmtScheme name="Office">"#),
            "应使用 raw_xml 路径"
        );
        assert!(xml.contains("<a:customChild/>"), "应包含 raw_xml 内容");
    }

    /// 验证 `write_xml` 在 raw_xml 与结构化字段都为空时使用默认 Office 格式方案。
    #[test]
    fn fmt_scheme_write_xml_uses_default_when_both_empty() {
        let fmt = FormatScheme::default();
        // name / raw_xml / 4 个结构化字段全部为空

        let mut w = XmlWriter::new();
        fmt.write_xml(&mut w);
        let xml = &w.buf;

        // 应使用 DEFAULT_FMT_SCHEME（直接 raw 输出，无 a:fmtScheme 包裹）
        assert!(xml.contains("<a:fmtScheme"), "应包含默认 fmtScheme");
        assert!(
            xml.contains("<a:fillStyleLst>"),
            "默认方案应含 fillStyleLst"
        );
        assert!(xml.contains("<a:lnStyleLst>"), "默认方案应含 lnStyleLst");
        assert!(
            xml.contains("<a:effectStyleLst>"),
            "默认方案应含 effectStyleLst"
        );
        assert!(
            xml.contains("<a:bgFillStyleLst>"),
            "默认方案应含 bgFillStyleLst"
        );
    }

    /// 验证默认 Office 主题 XML 中的 fmtScheme 能被正确结构化解析。
    ///
    /// 这是 round-trip 测试：默认方案的 fillStyleLst 应有 3 个元素，
    /// lnStyleLst 应有 3 个，effectStyleLst 应有 3 个，bgFillStyleLst 应有 3 个。
    #[test]
    fn fmt_scheme_parse_default_office_format_scheme() {
        // 从默认 Office 主题 XML 中提取 fmtScheme 内部内容
        let theme_xml = default_theme_xml();
        let fmt_start = theme_xml
            .find("<a:fmtScheme")
            .expect("默认主题应包含 <a:fmtScheme");
        let fmt_end = theme_xml
            .find("</a:fmtScheme>")
            .expect("默认主题应包含 </a:fmtScheme>");
        // 提取 <a:fmtScheme> 到 </a:fmtScheme> 之间的内部内容（不含根元素）
        let inner_start = theme_xml[fmt_start..].find('>').unwrap() + fmt_start + 1;
        let fmt_inner = &theme_xml[inner_start..fmt_end];

        let mut fmt = FormatScheme {
            raw_xml: fmt_inner.to_string(),
            ..Default::default()
        };
        fmt.parse_from_raw_xml();

        // 默认 Office 主题：每个 style 列表都有 3 个子元素
        assert_eq!(
            fmt.fill_style_count(),
            3,
            "默认 fillStyleLst 应有 3 个子元素"
        );
        assert_eq!(fmt.line_style_count(), 3, "默认 lnStyleLst 应有 3 个子元素");
        assert_eq!(
            fmt.effect_style_count(),
            3,
            "默认 effectStyleLst 应有 3 个子元素"
        );
        assert_eq!(
            fmt.bg_fill_style_count(),
            3,
            "默认 bgFillStyleLst 应有 3 个子元素"
        );
    }

    /// 验证结构化字段可被直接修改（替换某个 fill style）。
    #[test]
    fn fmt_scheme_structured_fields_are_mutable() {
        let mut fmt = FormatScheme {
            raw_xml: sample_fmt_scheme_raw_xml().to_string(),
            ..Default::default()
        };
        fmt.parse_from_raw_xml();

        // 替换第一个 fill style 为自定义 XML
        fmt.fill_styles[0] = r#"<a:noFill/>"#.to_string();

        let mut w = XmlWriter::new();
        fmt.write_xml(&mut w);
        let xml = &w.buf;

        // 验证替换后的内容出现在输出中
        assert!(
            xml.contains("<a:noFill/>"),
            "替换后的 fill style 应出现在输出"
        );
        // 验证其他 fill style 仍然存在
        assert!(xml.contains("<a:gradFill"), "其他 fill style 应保留");
    }

    /// 验证自闭合子元素（Empty 事件）能被正确收集为完整 XML 字符串。
    #[test]
    fn fmt_scheme_collect_self_closing_children() {
        // raw_xml 中 fillStyleLst 内只有自闭合子元素
        let raw = r#"<a:fillStyleLst><a:noFill/><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:fillStyleLst>"#;
        let children = collect_style_lst_children(raw, "fillStyleLst");
        assert_eq!(children.len(), 2, "应有 2 个子元素");
        // 第一个是自闭合，应被重构为 <a:noFill/>
        assert_eq!(children[0], "<a:noFill/>", "自闭子元素应重构为完整 XML");
        // 第二个是带子元素的
        assert!(
            children[1].starts_with("<a:solidFill>"),
            "第二个子元素应为 solidFill"
        );
    }

    /// 验证 `local_name_quick` 正确处理带命名空间前缀的元素名。
    #[test]
    fn local_name_quick_handles_namespaced_elements() {
        assert_eq!(local_name_quick(b"a:fillStyleLst"), b"fillStyleLst");
        assert_eq!(local_name_quick(b"fillStyleLst"), b"fillStyleLst");
        assert_eq!(local_name_quick(b"a:ln"), b"ln");
    }

    /// 验证 `collect_style_lst_children` 在容器不存在时返回空列表。
    #[test]
    fn collect_style_lst_children_returns_empty_when_container_missing() {
        let raw = r#"<a:otherLst><a:child/></a:otherLst>"#;
        let children = collect_style_lst_children(raw, "fillStyleLst");
        assert!(children.is_empty(), "容器不存在时应返回空列表");
    }
}
