using Ink_Canvas.Controls;
using Ink_Canvas.Helpers;
using Ink_Canvas.Properties;
using Ink_Canvas.UInk;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using System.Xml;
using System.Xml.Linq;
using Color = System.Drawing.Color;
using File = System.IO.File;
using Image = System.Windows.Controls.Image;
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;

namespace Ink_Canvas
{
    // 1. 定义元素信息结构
    public class CanvasElementInfo
    {
        public string Type { get; set; } // "Image" | "Pdf" | "Media"
        public string SourcePath { get; set; }
        public double Left { get; set; }
        public double Top { get; set; }
        public double Width { get; set; }
        public double Height { get; set; }
        public string Stretch { get; set; } = "Fill"; // 默认为Fill
        public string MediaKind { get; set; }
        public string MediaDisplayName { get; set; }
        public double? MediaPositionSeconds { get; set; }
        public double? MediaSpeedRatio { get; set; }
        public double? MediaVolume { get; set; }
        /// <summary>PDF 当前页（从 0 开始），仅 Type == Pdf 时有效。</summary>
        public int? PdfCurrentPage { get; set; }
        /// <summary>保存时的 PDF 总页数，用于校验；仅 Type == Pdf 时有效。</summary>
        public int? PdfPageCount { get; set; }
    }
    public partial class MainWindow : Ink_Canvas.Helpers.PerformanceTransparentWin
    {
        /// <summary>收集画布上图片与 PDF 的元数据，写入 .elements.json（与墨迹文件同路径）。</summary>
        private void CollectCanvasElementsMetadata(List<CanvasElementInfo> elementInfos)
        {
            if (elementInfos == null || inkCanvas == null) return;

            foreach (var child in inkCanvas.Children)
            {
                if (child is Image img && img.Source is BitmapImage bmp)
                {
                    elementInfos.Add(new CanvasElementInfo
                    {
                        Type = "Image",
                        SourcePath = bmp.UriSource?.LocalPath ?? "",
                        Left = InkCanvas.GetLeft(img),
                        Top = InkCanvas.GetTop(img),
                        Width = img.Width,
                        Height = img.Height,
                        Stretch = img.Stretch.ToString()
                    });
                }
                else if (child is PdfEmbeddedView pdf && !string.IsNullOrEmpty(pdf.PdfPath))
                {
                    elementInfos.Add(new CanvasElementInfo
                    {
                        Type = "Pdf",
                        SourcePath = pdf.PdfPath,
                        Left = InkCanvas.GetLeft(pdf),
                        Top = InkCanvas.GetTop(pdf),
                        Width = pdf.Width,
                        Height = pdf.Height,
                        Stretch = "Uniform",
                        PdfCurrentPage = (int)pdf.CurrentPageIndex,
                        PdfPageCount = (int)pdf.PageCount
                    });
                }
                else if (child is CanvasMediaControl mediaControl && TryGetMediaSourcePath(mediaControl, out var mediaSourcePath))
                {
                    string extension = Path.GetExtension(mediaSourcePath);
                    var mediaPosition = mediaControl.GetPlaybackPositionOrNull();
                    elementInfos.Add(new CanvasElementInfo
                    {
                        Type = "Media",
                        SourcePath = mediaSourcePath,
                        Left = InkCanvas.GetLeft(mediaControl),
                        Top = InkCanvas.GetTop(mediaControl),
                        Width = mediaControl.Width,
                        Height = mediaControl.Height,
                        Stretch = "Uniform",
                        MediaKind = mediaControl.IsAudioOnly ? "Audio" : "Video",
                        MediaDisplayName = mediaControl.DisplayName,
                        MediaPositionSeconds = mediaPosition?.TotalSeconds,
                        MediaSpeedRatio = mediaControl.PlaybackRate,
                        MediaVolume = mediaControl.VolumeLevel
                    });
                }
                else if (child is MediaElement media && media.Source != null)
                {
                    string sourcePath = media.Source.IsFile ? media.Source.LocalPath : media.Source.OriginalString;
                    if (!string.IsNullOrEmpty(sourcePath))
                    {
                        string extension = Path.GetExtension(sourcePath);
                        elementInfos.Add(new CanvasElementInfo
                        {
                            Type = "Media",
                            SourcePath = sourcePath,
                            Left = InkCanvas.GetLeft(media),
                            Top = InkCanvas.GetTop(media),
                            Width = media.Width,
                            Height = media.Height,
                            Stretch = media.Stretch.ToString(),
                            MediaKind = string.Equals(extension, ".mp3", StringComparison.OrdinalIgnoreCase)
                                || string.Equals(extension, ".wav", StringComparison.OrdinalIgnoreCase)
                                || string.Equals(extension, ".m4a", StringComparison.OrdinalIgnoreCase)
                                || string.Equals(extension, ".aac", StringComparison.OrdinalIgnoreCase)
                                || string.Equals(extension, ".flac", StringComparison.OrdinalIgnoreCase) ? "Audio" : "Video"
                        });
                    }
                }
            }
        }

        private void RestoreMediaFromElementInfo(CanvasElementInfo info)
        {
            if (info == null || inkCanvas == null) return;
            if (!string.Equals(info.Type, "Media", StringComparison.OrdinalIgnoreCase)) return;
            if (string.IsNullOrEmpty(info.SourcePath) || !File.Exists(info.SourcePath)) return;

            try
            {
                var mediaControl = new CanvasMediaControl
                {
                    Name = "media_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff"),
                    Width = info.Width > 0 && !double.IsNaN(info.Width) ? info.Width : 800,
                    Height = info.Height > 0 && !double.IsNaN(info.Height) ? info.Height : (string.Equals(info.MediaKind, "Audio", StringComparison.OrdinalIgnoreCase) ? 168 : 520),
                    ToolTip = string.IsNullOrWhiteSpace(info.MediaDisplayName) ? Path.GetFileName(info.SourcePath) : info.MediaDisplayName
                };
                mediaControl.Initialize(info.SourcePath, info.MediaDisplayName);
                if (info.MediaSpeedRatio.HasValue)
                {
                    mediaControl.SetPlaybackRate(info.MediaSpeedRatio.Value);
                }
                if (info.MediaVolume.HasValue)
                {
                    mediaControl.SetVolumeLevel(info.MediaVolume.Value);
                }
                if (info.MediaPositionSeconds.HasValue)
                {
                    mediaControl.SetPlaybackPosition(TimeSpan.FromSeconds(Math.Max(0, info.MediaPositionSeconds.Value)));
                }

                AttachMediaFailureNotification(mediaControl);
                InkCanvas.SetLeft(mediaControl, info.Left);
                InkCanvas.SetTop(mediaControl, info.Top);
                InitializeElementTransform(mediaControl);
                BindElementEvents(mediaControl);
                inkCanvas.Children.Add(mediaControl);
            }
            catch (Exception ex)
            {
                LogHelper.WriteLogToFile($"从 .elements.json 恢复媒体失败: {ex.Message}", LogHelper.LogType.Error);
            }
        }

        /// <summary>
        /// 保存墨迹的鼠标释放事件处理
        /// </summary>
        /// <param name="sender">发送者</param>
        /// <param name="e">鼠标按钮事件参数</param>
        /// <remarks>
        /// 该方法会：
        /// 1. 检查是否是当前按下的对象，且墨迹画布是否可见
        /// 2. 隐藏工具面板
        /// 3. 隐藏通知面板
        /// 4. 调用SaveInkCanvasStrokes方法保存墨迹
        /// </remarks>
        private void SymbolIconSaveStrokes_MouseUp(object sender, MouseButtonEventArgs e)
        {
            if (lastBorderMouseDownObject != sender || inkCanvas.Visibility != Visibility.Visible) return;

            AnimationsHelper.HidePopupWithSlideAndFade(BorderTools);
            AnimationsHelper.HidePopupWithSlideAndFade(BoardBorderToolsPopup);

            GridNotifications.Visibility = Visibility.Collapsed;

            SaveInkCanvasStrokes(true, true);
        }

        /// <summary>
        /// 保存墨迹画布的墨迹
        /// </summary>
        /// <param name="newNotice">是否显示新的通知</param>
        /// <param name="saveByUser">是否是用户手动保存</param>
        /// <remarks>
        /// 该方法会：
        /// 1. 根据保存类型和模式确定保存路径
        /// 2. 创建保存目录
        /// 3. 根据当前模式生成保存文件名
        /// 4. 根据设置选择保存模式：
        ///    - 全页面保存模式：保存为图像或压缩包
        ///    - XML保存模式：保存为XML文件或压缩包
        ///    - 常规保存模式：保存为二进制格式或XML格式
        /// 5. 异步上传保存的文件到Dlass
        /// 6. 保存元素信息
        /// </remarks>
        public void SaveInkCanvasStrokes(bool newNotice = true, bool saveByUser = false)
        {
            try
            {
                var savePath = Settings.Automation.AutoSavedStrokesLocation
                               + (saveByUser ? @"\User Saved - " : @"\Auto Saved - ")
                               + (currentMode == 0 ? "Annotation Strokes" : "BlackBoard Strokes");
                if (!Directory.Exists(savePath)) Directory.CreateDirectory(savePath);
                string savePathWithName;
                if (Settings.Automation.IsUseCustomSaveFileName)
                {
                    var ctx = new SaveFileNameContext
                    {
                        Mode = currentMode == 0 ? "Annotation" : "BlackBoard",
                        Type = saveByUser ? "User" : "Auto",
                        Page = currentMode != 0 ? CurrentWhiteboardIndex : null,
                        Count = inkCanvas.Strokes.Count
                    };
                    var fname = SaveFileNameHelper.Render(Settings.Automation.CustomSaveFileNameTemplate, ctx);
                    savePathWithName = savePath + @"\" + fname + ".icstk";
                }
                else if (currentMode != 0) // 黑板模式下
                    savePathWithName = savePath + @"\" + DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss-fff") + " Page-" +
                                       CurrentWhiteboardIndex + " StrokesCount-" + inkCanvas.Strokes.Count + ".icstk";
                else
                    //savePathWithName = savePath + @"\" + DateTime.Now.ToString("u").Replace(':', '-') + ".icstk";
                    savePathWithName = savePath + @"\" + DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss-fff") + ".icstk";

                if (Settings.Automation.IsSaveStrokesAsUInK)
                {
                    // UInk 1.0 格式保存（跨软件互操作），走两阶段提交
                    SaveCurrentStateToUInk(Path.ChangeExtension(savePathWithName, ".uink"), newNotice);
                    return;
                }

                if (Settings.Automation.IsSaveStrokesAsXML)
                {
                    // XML保存模式 - 检查是否存在多页面墨迹
                    bool hasMultiplePages = false;
                    List<StrokeCollection> allPageStrokes = new List<StrokeCollection>();

                    // 检查PPT放映模式下的多页面墨迹
                    if (IsInPPTPresentationMode && _pptManager?.IsConnected == true)
                    {
                        hasMultiplePages = true;
                        var totalSlides = _pptManager.SlidesCount;
                        var currentSlide = _pptManager.GetCurrentSlideNumber();

                        for (int i = 1; i <= totalSlides; i++)
                        {
                            var slideStrokes = _singlePPTInkManager?.LoadSlideStrokes(i);
                            if (slideStrokes != null && slideStrokes.Count > 0)
                            {
                                allPageStrokes.Add(slideStrokes);
                            }
                            else if (i == currentSlide && inkCanvas.Strokes.Count > 0)
                            {
                                allPageStrokes.Add(inkCanvas.Strokes.Clone());
                            }
                            else
                            {
                                allPageStrokes.Add(new StrokeCollection());
                            }
                        }
                    }
                    // 检查白板模式下的多页面墨迹
                    else if (currentMode != 0 && WhiteboardTotalCount > 1)
                    {
                        hasMultiplePages = true;
                        for (int i = 1; i <= WhiteboardTotalCount; i++)
                        {
                            if (TimeMachineHistories[i] != null)
                            {
                                var strokes = ApplyHistoriesToNewStrokeCollection(TimeMachineHistories[i]);
                                allPageStrokes.Add(strokes);
                            }
                            else
                            {
                                allPageStrokes.Add(new StrokeCollection());
                            }
                        }
                    }

                    if (hasMultiplePages && allPageStrokes.Count > 0)
                    {
                        // 检查是否是PPT模式
                        bool isPPTMode = IsInPPTPresentationMode && _pptManager?.IsConnected == true;

                        if (isPPTMode)
                        {
                            // PPT模式：保存为多个XML文件
                            string basePath = Path.GetDirectoryName(savePathWithName);
                            string baseFileName = Path.GetFileNameWithoutExtension(savePathWithName);

                            int savedCount = 0;
                            for (int i = 0; i < allPageStrokes.Count; i++)
                            {
                                var strokes = allPageStrokes[i];
                                if (strokes.Count > 0)
                                {
                                    string pageFileName = Path.Combine(basePath, $"{baseFileName}_Page-{i + 1}.xml");
                                    SaveStrokesAsXML(strokes, pageFileName, false);
                                    savedCount++;

                                    // 异步上传每个XML文件
                                    _ = Task.Run(async () =>
                                    {
                                        try
                                        {
                                            await Helpers.UploadHelper.UploadFileAsync(pageFileName);
                                        }
                                        catch (Exception)
                                        {
                                        }
                                    });
                                }
                            }

                            if (newNotice)
                            {
                                Task.Delay(100).ContinueWith(t =>
                                {
                                    Dispatcher.Invoke(() =>
                                    {
                                        ShowNotification(string.Format(MainWindowStrings.Main_Strokes_SaveMultiPageXmlSuccess, savedCount));
                                    });
                                });
                            }
                        }
                        else
                        {
                            // 非PPT模式：保存为XML压缩包
                            string zipFileName = Path.ChangeExtension(savePathWithName, "zip");
                            SaveMultiPageStrokesAsXMLZip(allPageStrokes, zipFileName, newNotice);
                        }
                    }
                    else
                    {
                        // 单页面XML保存
                        string xmlPath = Path.ChangeExtension(savePathWithName, ".xml");
                        SaveStrokesAsXML(inkCanvas.Strokes, xmlPath);
                        if (newNotice)
                        {
                            Task.Delay(100).ContinueWith(t =>
                            {
                                Dispatcher.Invoke(() =>
                                {
                                    ShowNotification(string.Format(MainWindowStrings.Main_Strokes_SaveXmlSuccess, xmlPath));
                                });
                            });
                        }
                    }
                }
                else if (Settings.Automation.IsSaveFullPageStrokes)
                {
                    // 全页面保存模式 - 检查是否存在多页面墨迹
                    bool hasMultiplePages = false;
                    List<StrokeCollection> allPageStrokes = new List<StrokeCollection>();

                    // 检查PPT放映模式下的多页面墨迹
                    if (IsInPPTPresentationMode && _pptManager?.IsConnected == true)
                    {
                        hasMultiplePages = true;
                        // 收集PPT放映模式下的所有页面墨迹
                        var totalSlides = _pptManager.SlidesCount;
                        var currentSlide = _pptManager.GetCurrentSlideNumber();

                        for (int i = 1; i <= totalSlides; i++)
                        {
                            var slideStrokes = _singlePPTInkManager?.LoadSlideStrokes(i);
                            if (slideStrokes != null && slideStrokes.Count > 0)
                            {
                                allPageStrokes.Add(slideStrokes);
                            }
                            else if (i == currentSlide && inkCanvas.Strokes.Count > 0)
                            {
                                // 当前页面的墨迹
                                allPageStrokes.Add(inkCanvas.Strokes.Clone());
                            }
                            else
                            {
                                allPageStrokes.Add(new StrokeCollection()); // 空页面
                            }
                        }
                    }
                    // 检查白板模式下的多页面墨迹
                    else if (currentMode != 0 && WhiteboardTotalCount > 1)
                    {
                        hasMultiplePages = true;
                        // 收集白板模式下的所有页面墨迹
                        for (int i = 1; i <= WhiteboardTotalCount; i++)
                        {
                            if (TimeMachineHistories[i] != null)
                            {
                                // 从历史记录中恢复墨迹
                                var strokes = ApplyHistoriesToNewStrokeCollection(TimeMachineHistories[i]);
                                allPageStrokes.Add(strokes);
                            }
                            else
                            {
                                allPageStrokes.Add(new StrokeCollection()); // 空页面
                            }
                        }
                    }

                    if (hasMultiplePages && allPageStrokes.Count > 0)
                    {
                        // 多页面墨迹保存为压缩包
                        string zipFileName = Path.ChangeExtension(savePathWithName, "zip");
                        SaveMultiPageStrokesAsZip(allPageStrokes, zipFileName, newNotice);
                    }
                    else
                    {
                        // 单页面墨迹保存为图像
                        SaveSinglePageStrokesAsImage(savePathWithName, newNotice);
                    }
                }
                else
                {
                    // 常规保存模式 - 检查是否存在多页面墨迹
                    bool hasMultiplePages = false;
                    List<StrokeCollection> allPageStrokes = new List<StrokeCollection>();

                    // 检查PPT放映模式下的多页面墨迹
                    if (IsInPPTPresentationMode && _pptManager?.IsConnected == true)
                    {
                        hasMultiplePages = true;
                        var totalSlides = _pptManager.SlidesCount;
                        var currentSlide = _pptManager.GetCurrentSlideNumber();

                        for (int i = 1; i <= totalSlides; i++)
                        {
                            var slideStrokes = _singlePPTInkManager?.LoadSlideStrokes(i);
                            if (slideStrokes != null && slideStrokes.Count > 0)
                            {
                                allPageStrokes.Add(slideStrokes);
                            }
                            else if (i == currentSlide && inkCanvas.Strokes.Count > 0)
                            {
                                allPageStrokes.Add(inkCanvas.Strokes.Clone());
                            }
                            else
                            {
                                allPageStrokes.Add(new StrokeCollection());
                            }
                        }
                    }
                    // 检查白板模式下的多页面墨迹
                    else if (currentMode != 0 && WhiteboardTotalCount > 1)
                    {
                        hasMultiplePages = true;
                        for (int i = 1; i <= WhiteboardTotalCount; i++)
                        {
                            if (TimeMachineHistories[i] != null)
                            {
                                var strokes = ApplyHistoriesToNewStrokeCollection(TimeMachineHistories[i]);
                                allPageStrokes.Add(strokes);
                            }
                            else
                            {
                                allPageStrokes.Add(new StrokeCollection());
                            }
                        }
                    }

                    if (hasMultiplePages && allPageStrokes.Count > 0)
                    {
                        // 多页面保存为多个icstk文件
                        string basePath = Path.GetDirectoryName(savePathWithName);
                        string baseFileName = Path.GetFileNameWithoutExtension(savePathWithName);

                        for (int i = 0; i < allPageStrokes.Count; i++)
                        {
                            var strokes = allPageStrokes[i];
                            if (strokes.Count > 0)
                            {
                                string pageFileName = Path.Combine(basePath, $"{baseFileName}_Page-{i + 1}.icstk");
                                using (var fs = new FileStream(pageFileName, FileMode.Create))
                                {
                                    strokes.Save(fs);
                                }

                                // 异步上传每个icstk文件
                                _ = Task.Run(async () =>
                                {
                                    try
                                    {
                                        await Helpers.UploadHelper.UploadFileAsync(pageFileName);
                                    }
                                    catch (Exception)
                                    {
                                    }
                                });
                            }
                        }

                        if (newNotice)
                        {
                            Task.Delay(100).ContinueWith(t =>
                            {
                                Dispatcher.Invoke(() =>
                                {
                                    ShowNotification(string.Format(MainWindowStrings.Main_Strokes_SaveMultiPageIcstkSuccess, allPageStrokes.Count));
                                });
                            });
                        }
                    }
                    else
                    {
                        // 单页面保存
                        if (Settings.Automation.IsSaveStrokesAsXML)
                        {
                            // 保存为XML格式
                            string xmlPath = Path.ChangeExtension(savePathWithName, ".xml");
                            SaveStrokesAsXML(inkCanvas.Strokes, xmlPath);
                            if (newNotice)
                            {
                                Task.Delay(100).ContinueWith(t =>
                                {
                                    Dispatcher.Invoke(() =>
                                    {
                                        ShowNotification(string.Format(MainWindowStrings.Main_Strokes_SaveXmlSuccess, xmlPath));
                                    });
                                });
                            }
                        }
                        else
                        {
                            // 保存为二进制格式：先写临时文件后 Replace 原子替换，
                            // 避免 FileMode.Create 直接截断 → 写入中途失败 → 文件停在 0 字节。
                            // 同目录下移动替换是原子操作，Windows 同卷 NTFS 保证。
                            var tmpPath = savePathWithName + ".tmp";
                            try
                            {
                                using (var fs = new FileStream(tmpPath, FileMode.Create))
                                {
                                    inkCanvas.Strokes.Save(fs);
                                }
                                if (File.Exists(savePathWithName))
                                    File.Replace(tmpPath, savePathWithName, null);
                                else
                                    File.Move(tmpPath, savePathWithName);
                            }
                            catch
                            {
                                try { if (File.Exists(tmpPath)) File.Delete(tmpPath); } catch { }
                                throw;
                            }
                            if (newNotice)
                            {
                                Task.Delay(100).ContinueWith(t =>
                                {
                                    Dispatcher.Invoke(() =>
                                    {
                                        ShowNotification(string.Format(MainWindowStrings.Main_Strokes_SaveSuccess, savePathWithName));
                                    });
                                });
                            }
                        }

                        // 异步上传文件
                        _ = Task.Run(async () =>
                        {
                            try
                            {
                                string uploadPath = Settings.Automation.IsSaveStrokesAsXML ? Path.ChangeExtension(savePathWithName, ".xml") : savePathWithName;
                                await Helpers.UploadHelper.UploadFileAsync(uploadPath);
                            }
                            catch (Exception)
                            {
                            }
                        });

                        // 保存元素信息
                        var elementInfos = new List<CanvasElementInfo>();
                        CollectCanvasElementsMetadata(elementInfos);
                        string elementsPath = Settings.Automation.IsSaveStrokesAsXML ? Path.ChangeExtension(savePathWithName, ".elements.json") : Path.ChangeExtension(savePathWithName, ".elements.json");
                        File.WriteAllText(elementsPath, JsonConvert.SerializeObject(elementInfos, Newtonsoft.Json.Formatting.Indented));
                    }
                }
            }
            catch (Exception ex)
            {
                ShowNotification(MainWindowStrings.Main_Strokes_SaveFailed);
                LogHelper.WriteLogToFile("墨迹保存失败 | " + ex, LogHelper.LogType.Error);
            }
        }

        /// <summary>
        /// 将StrokeCollection保存为XML格式
        /// </summary>
        private void SaveStrokesAsXML(StrokeCollection strokes, string xmlPath, bool triggerUpload = true)
        {
            try
            {
                // 使用XDocument创建XML文档
                XDocument doc = new XDocument(
                    new XDeclaration("1.0", "utf-8", "yes"),
                    new XElement("InkCanvasStrokes",
                        new XAttribute("Version", "1.0"),
                        new XAttribute("StrokeCount", strokes.Count),
                        new XAttribute("SaveTime", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")),
                        from stroke in strokes
                        select new XElement("Stroke",
                            new XAttribute("DrawingAttributes", SerializeDrawingAttributes(stroke.DrawingAttributes)),
                            new XElement("StylusPoints",
                                from point in stroke.StylusPoints
                                select new XElement("StylusPoint",
                                    new XAttribute("X", point.X),
                                    new XAttribute("Y", point.Y),
                                    new XAttribute("PressureFactor", point.PressureFactor)
                                )
                            )
                        )
                    )
                );

                // 保存XML文件
                using (var writer = new XmlTextWriter(xmlPath, Encoding.UTF8))
                {
                    writer.Formatting = System.Xml.Formatting.Indented;
                    doc.Save(writer);
                }

                // 同时保存元素信息
                var elementInfos = new List<CanvasElementInfo>();
                CollectCanvasElementsMetadata(elementInfos);
                File.WriteAllText(Path.ChangeExtension(xmlPath, ".elements.json"), JsonConvert.SerializeObject(elementInfos, Newtonsoft.Json.Formatting.Indented));

                // 异步上传到Dlass
                if (triggerUpload)
                {
                    _ = Task.Run(async () =>
                    {
                        try
                        {
                            await Helpers.UploadHelper.UploadFileAsync(xmlPath);
                        }
                        catch (Exception)
                        {
                        }
                    });
                }
            }
            catch (Exception ex)
            {
                LogHelper.WriteLogToFile($"保存XML格式墨迹失败: {ex}", LogHelper.LogType.Error);
                throw;
            }
        }

        /// <summary>
        /// 序列化DrawingAttributes为字符串
        /// </summary>
        private string SerializeDrawingAttributes(DrawingAttributes da)
        {
            var sb = new StringBuilder();
            sb.Append($"Color={da.Color};");
            sb.Append($"Width={da.Width};");
            sb.Append($"Height={da.Height};");
            sb.Append($"FitToCurve={da.FitToCurve};");
            sb.Append($"IsHighlighter={da.IsHighlighter};");
            sb.Append($"IgnorePressure={da.IgnorePressure};");
            sb.Append($"StylusTip={da.StylusTip};");
            return sb.ToString();
        }

        /// <summary>
        /// 将多页面墨迹保存为XML格式压缩包
        /// </summary>
        private void SaveMultiPageStrokesAsXMLZip(List<StrokeCollection> allPageStrokes, string zipFileName, bool newNotice)
        {
            try
            {
                // 创建临时目录来存放文件
                string tempDir = Path.Combine(Path.GetTempPath(), $"InkCanvas_MultiPage_XML_{DateTime.Now:yyyyMMdd_HHmmss}");
                Directory.CreateDirectory(tempDir);

                try
                {
                    // 保存所有页面的XML文件到临时目录
                    for (int i = 0; i < allPageStrokes.Count; i++)
                    {
                        var strokes = allPageStrokes[i];
                        if (strokes.Count > 0)
                        {
                            // 保存XML文件（临时文件，不触发上传）
                            string xmlFileName = Path.Combine(tempDir, $"page_{i + 1:D4}.xml");
                            SaveStrokesAsXML(strokes, xmlFileName, false);
                        }
                    }

                    // 保存元数据信息
                    string metadataFile = Path.Combine(tempDir, "metadata.txt");
                    using (var writer = new StreamWriter(metadataFile, false, Encoding.UTF8))
                    {
                        writer.WriteLine($"保存时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
                        writer.WriteLine($"总页数: {allPageStrokes.Count}");
                        writer.WriteLine($"模式: {(currentMode == 0 ? "PPT放映" : FloatingBarStrings.FloatingBar_Whiteboard)}");
                        writer.WriteLine($"格式: XML");
                        if (currentMode != 0)
                        {
                            writer.WriteLine($"当前页面: {CurrentWhiteboardIndex}");
                            writer.WriteLine($"总页面数: {WhiteboardTotalCount}");
                        }
                        else if (pptApplication != null)
                        {
                            writer.WriteLine($"PPT名称: {pptApplication.SlideShowWindows[1].Presentation.Name}");
                            writer.WriteLine($"PPT总页数: {pptApplication.SlideShowWindows[1].Presentation.Slides.Count}");
                            writer.WriteLine($"PPT文件路径: {pptApplication.SlideShowWindows[1].Presentation.FullName}");
                        }

                        for (int i = 0; i < allPageStrokes.Count; i++)
                        {
                            writer.WriteLine($"页面 {i + 1}: {allPageStrokes[i].Count} 条墨迹");
                        }
                    }

                    // 创建ZIP文件
                    if (File.Exists(zipFileName))
                        File.Delete(zipFileName);

                    ZipFile.CreateFromDirectory(tempDir, zipFileName);

                    // 异步上传ZIP文件到Dlass
                    _ = Task.Run(async () =>
                    {
                        try
                        {
                            await Helpers.UploadHelper.UploadFileAsync(zipFileName);
                        }
                        catch (Exception)
                        {
                        }
                    });

                    if (newNotice)
                    {
                        Task.Delay(100).ContinueWith(t =>
                        {
                            Dispatcher.Invoke(() =>
                            {
                                ShowNotification(string.Format(MainWindowStrings.Main_Strokes_SaveMultiPageXmlZipSuccess, zipFileName));
                            });
                        });
                    }
                }
                finally
                {
                    // 清理临时目录
                    try
                    {
                        if (Directory.Exists(tempDir))
                            Directory.Delete(tempDir, true);
                    }
                    catch (Exception ex)
                    {
                        LogHelper.WriteLogToFile($"清理临时目录失败: {ex}", LogHelper.LogType.Warning);
                    }
                }
            }
            catch (Exception ex)
            {
                LogHelper.WriteLogToFile($"保存多页面XML墨迹压缩包失败: {ex}", LogHelper.LogType.Error);
                throw;
            }
        }

        /// <summary>
        /// 将多页面墨迹保存为压缩包
        /// </summary>
        private void SaveMultiPageStrokesAsZip(List<StrokeCollection> allPageStrokes, string zipFileName, bool newNotice)
        {
            try
            {
                // 创建临时目录来存放文件
                string tempDir = Path.Combine(Path.GetTempPath(), $"InkCanvas_MultiPage_{DateTime.Now:yyyyMMdd_HHmmss}");
                Directory.CreateDirectory(tempDir);

                try
                {
                    // 保存所有页面的文件到临时目录
                    for (int i = 0; i < allPageStrokes.Count; i++)
                    {
                        var strokes = allPageStrokes[i];
                        if (strokes.Count > 0)
                        {
                            // 保存墨迹文件
                            string strokeFileName = Path.Combine(tempDir, $"page_{i + 1:D4}.icstk");
                            using (var fs = new FileStream(strokeFileName, FileMode.Create))
                            {
                                strokes.Save(fs);
                            }

                            // 保存页面图像
                            string imageFileName = Path.Combine(tempDir, $"page_{i + 1:D4}.png");
                            using (var fs = new FileStream(imageFileName, FileMode.Create))
                            {
                                SavePageAsImage(strokes, fs);
                            }
                        }
                    }

                    // 保存元数据信息
                    string metadataFile = Path.Combine(tempDir, "metadata.txt");
                    using (var writer = new StreamWriter(metadataFile, false, Encoding.UTF8))
                    {
                        writer.WriteLine($"保存时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
                        writer.WriteLine($"总页数: {allPageStrokes.Count}");
                        writer.WriteLine($"模式: {(currentMode == 0 ? "PPT放映" : FloatingBarStrings.FloatingBar_Whiteboard)}");
                        if (currentMode != 0)
                        {
                            writer.WriteLine($"当前页面: {CurrentWhiteboardIndex}");
                            writer.WriteLine($"总页面数: {WhiteboardTotalCount}");
                        }
                        else if (pptApplication != null)
                        {
                            writer.WriteLine($"PPT名称: {pptApplication.SlideShowWindows[1].Presentation.Name}");
                            writer.WriteLine($"PPT总页数: {pptApplication.SlideShowWindows[1].Presentation.Slides.Count}");
                            writer.WriteLine($"PPT文件路径: {pptApplication.SlideShowWindows[1].Presentation.FullName}");
                        }

                        for (int i = 0; i < allPageStrokes.Count; i++)
                        {
                            writer.WriteLine($"页面 {i + 1}: {allPageStrokes[i].Count} 条墨迹");
                        }
                    }

                    // 使用.NET Framework内置的压缩功能创建ZIP文件
                    if (File.Exists(zipFileName))
                        File.Delete(zipFileName);

                    // 使用System.IO.Compression.FileSystem来创建ZIP
                    ZipFile.CreateFromDirectory(tempDir, zipFileName);

                    // 异步上传ZIP文件到Dlass
                    _ = Task.Run(async () =>
                    {
                        try
                        {
                            await Helpers.UploadHelper.UploadFileAsync(zipFileName);
                        }
                        catch (Exception)
                        {
                        }
                    });

                    if (newNotice)
                    {
                        Task.Delay(100).ContinueWith(t =>
                        {
                            Dispatcher.Invoke(() =>
                            {
                                ShowNotification(string.Format(MainWindowStrings.Main_Strokes_SaveMultiPageZipSuccess, zipFileName));
                            });
                        });
                    }
                }
                finally
                {
                    // 清理临时目录
                    try
                    {
                        if (Directory.Exists(tempDir))
                            Directory.Delete(tempDir, true);
                    }
                    catch (Exception ex)
                    {
                        LogHelper.WriteLogToFile($"清理临时目录失败: {ex}", LogHelper.LogType.Warning);
                    }
                }
            }
            catch (Exception ex)
            {
                LogHelper.WriteLogToFile($"保存多页面墨迹压缩包失败: {ex}", LogHelper.LogType.Error);
                throw;
            }
        }

        /// <summary>
        /// 将单页面墨迹保存为图像
        /// </summary>
        private void SaveSinglePageStrokesAsImage(string savePathWithName, bool newNotice)
        {
            // 全页面保存模式 - 保存整个墨迹页面的图像
            using (var bitmap = new Bitmap(
                Screen.PrimaryScreen.Bounds.Width,
                Screen.PrimaryScreen.Bounds.Height))
            {

                using (var g = Graphics.FromImage(bitmap))
                {
                    // 创建黑色或透明背景
                    Color bgColor = Settings.Canvas.UsingWhiteboard
                        ? Color.White
                        : Color.FromArgb(22, 41, 36); // 黑板背景色
                    g.Clear(bgColor);

                    // 将InkCanvas墨迹渲染到Visual
                    var visual = new DrawingVisual();
                    using (var dc = visual.RenderOpen())
                    {
                        // 创建一个VisualBrush，使用inkCanvas作为源
                        var visualBrush = new VisualBrush(inkCanvas);
                        // 绘制矩形并填充为inkCanvas的内容
                        dc.DrawRectangle(visualBrush, null, new Rect(0, 0, inkCanvas.ActualWidth, inkCanvas.ActualHeight));
                    }

                    // 创建适合墨迹画布尺寸的渲染位图
                    var rtb = new RenderTargetBitmap(
                        (int)inkCanvas.ActualWidth, (int)inkCanvas.ActualHeight,
                        96, 96,
                        PixelFormats.Pbgra32);
                    rtb.Render(visual);

                    // 转换为GDI+ Bitmap并保存
                    var encoder = new PngBitmapEncoder();
                    encoder.Frames.Add(BitmapFrame.Create(rtb));

                    using (var ms = new MemoryStream())
                    {
                        encoder.Save(ms);
                        ms.Seek(0, SeekOrigin.Begin);
                        using (var imgBitmap = new Bitmap(ms))
                        {

                            // 将生成的墨迹图像绘制到屏幕截图上
                            // 居中绘制，确保墨迹位于屏幕中央
                            int x = (bitmap.Width - imgBitmap.Width) / 2;
                            int y = (bitmap.Height - imgBitmap.Height) / 2;
                            g.DrawImage(imgBitmap, x, y);

                            // 保存为PNG
                            string imagePathWithName = Path.ChangeExtension(savePathWithName, "png");
                            bitmap.Save(imagePathWithName, ImageFormat.Png);

                            // 仍然保存墨迹文件以兼容旧版本
                            using (var fs = new FileStream(savePathWithName, FileMode.Create))
                            {
                                inkCanvas.Strokes.Save(fs);
                            }

                            _ = Task.Run(async () =>
                            {
                                try
                                {
                                    await Helpers.UploadHelper.UploadFileAsync(imagePathWithName);
                                }
                                catch (Exception)
                                {
                                }
                            });
                        } // using imgBitmap
                    }
                } // using g
            } // using bitmap

            // 显示提示
            if (newNotice)
            {
                Task.Delay(100).ContinueWith(t =>
                {
                    Dispatcher.Invoke(() =>
                    {
                        ShowNotification(string.Format(MainWindowStrings.Main_Strokes_SaveFullPageSuccess, Path.ChangeExtension(savePathWithName, "png")));
                    });
                });
            }
        }

        /// <summary>
        /// 将指定墨迹集合保存为图像到指定流
        /// </summary>
        private void SavePageAsImage(StrokeCollection strokes, Stream outputStream)
        {
            try
            {
                // 创建临时InkCanvas来渲染墨迹
                var tempCanvas = new InkCanvas();
                tempCanvas.Strokes = strokes;
                tempCanvas.Width = inkCanvas.ActualWidth;
                tempCanvas.Height = inkCanvas.ActualHeight;

                // 创建渲染位图
                var rtb = new RenderTargetBitmap(
                    (int)tempCanvas.Width, (int)tempCanvas.Height,
                    96, 96,
                    PixelFormats.Pbgra32);
                rtb.Render(tempCanvas);

                // 保存为PNG
                var encoder = new PngBitmapEncoder();
                encoder.Frames.Add(BitmapFrame.Create(rtb));
                encoder.Save(outputStream);
            }
            catch (Exception ex)
            {
                LogHelper.WriteLogToFile($"保存页面图像失败: {ex}", LogHelper.LogType.Error);
                throw;
            }
        }

        /// <summary>
        /// 供插件导出的当前画布页 PNG 入口（墨迹 + 背景色）。
        /// </summary>
        internal bool ExportCurrentPageAsPngForPlugin(string filePath)
        {
            try
            {
                if (string.IsNullOrWhiteSpace(filePath)) return false;
                SaveSinglePageStrokesAsImage(filePath, false);
                return File.Exists(filePath);
            }
            catch (Exception ex)
            {
                LogHelper.WriteLogToFile($"插件导出当前页 PNG 失败: {ex.Message}", LogHelper.LogType.Error);
                return false;
            }
        }

        /// <summary>
        /// 供插件导出的任意墨迹 PNG 入口。
        /// </summary>
        internal bool ExportStrokesAsPngForPlugin(StrokeCollection strokes, string filePath)
        {
            try
            {
                if (string.IsNullOrWhiteSpace(filePath) || strokes == null) return false;
                using (var stream = new FileStream(filePath, FileMode.Create, FileAccess.Write))
                {
                    SavePageAsImage(strokes, stream);
                }
                return File.Exists(filePath);
            }
            catch (Exception ex)
            {
                LogHelper.WriteLogToFile($"插件导出墨迹 PNG 失败: {ex.Message}", LogHelper.LogType.Error);
                return false;
            }
        }

        /// <summary>
        /// 打开墨迹文件的鼠标释放事件处理
        /// </summary>
        /// <param name="sender">发送者</param>
        /// <param name="e">鼠标按钮事件参数</param>
        /// <remarks>
        /// 该方法会：
        /// 1. 检查是否是当前按下的对象
        /// 2. 隐藏工具面板
        /// 3. 打开文件选择对话框
        /// 4. 根据文件扩展名选择不同的打开方式：
        ///    - .zip：处理ICC压缩包
        ///    - .xml：处理XML格式墨迹文件
        ///    - 其他：处理单个墨迹文件（二进制格式）
        /// 5. 如果墨迹画布不可见，切换到鼠标模式
        /// </remarks>
        private void SymbolIconOpenStrokes_MouseUp(object sender, MouseButtonEventArgs e)
        {
            if (TryBlockFrozenPageMutation("打开墨迹文件")) return;
            if (lastBorderMouseDownObject != sender) return;
            AnimationsHelper.HidePopupWithSlideAndFade(BorderTools);
            AnimationsHelper.HidePopupWithSlideAndFade(BoardBorderToolsPopup);

            var openFileDialog = new OpenFileDialog();
            openFileDialog.InitialDirectory = Settings.Automation.AutoSavedStrokesLocation;
            openFileDialog.Title = MainWindowStrings.Main_Strokes_OpenFileDialogTitle;
            openFileDialog.Filter = MainWindowStrings.Main_Strokes_OpenFileDialogFilter;
            if (openFileDialog.ShowDialog() != true) return;
            LogHelper.WriteLogToFile($"Strokes Insert: Name: {openFileDialog.FileName}",
                LogHelper.LogType.Event);

            try
            {
                string fileExtension = Path.GetExtension(openFileDialog.FileName).ToLower();

                if (fileExtension == ".uink")
                {
                    // UInk 1.0 墨迹文件（MessagePack 对象流 + .uink.extra 资源包）
                    OpenUInkFile(openFileDialog.FileName);
                }
                else if (fileExtension == ".zip")
                {
                    // 处理ICC压缩包（可能包含XML格式）
                    OpenICCZipFile(openFileDialog.FileName);
                }
                else if (fileExtension == ".xml")
                {
                    // 处理XML格式墨迹文件
                    OpenXMLStrokeFile(openFileDialog.FileName);
                }
                else
                {
                    // 处理单个墨迹文件（二进制格式）
                    OpenSingleStrokeFile(openFileDialog.FileName);
                }

                if (inkCanvas.Visibility != Visibility.Visible) SymbolIconCursor_Click(sender, null);
            }
            catch (Exception ex)
            {
                ShowNotification(MainWindowStrings.Main_Strokes_OpenFailed);
                LogHelper.WriteLogToFile($"墨迹打开失败: {ex}", LogHelper.LogType.Error);
            }
        }

        /// <summary>
        /// 打开ICC创建的.zip压缩包
        /// </summary>
        private void OpenICCZipFile(string zipFilePath)
        {
            try
            {
                // 创建临时目录来解压文件
                string tempDir = Path.Combine(Path.GetTempPath(), $"InkCanvas_Open_{DateTime.Now:yyyyMMdd_HHmmss}");
                Directory.CreateDirectory(tempDir);

                try
                {
                    // 解压ZIP文件
                    SafeZipExtractor.ExtractZipSafely(zipFilePath, tempDir, overwrite: true);

                    // 读取元数据文件
                    string metadataFile = Path.Combine(tempDir, "metadata.txt");
                    if (!File.Exists(metadataFile))
                    {
                        throw new Exception("压缩包中未找到元数据文件");
                    }

                    var metadata = ReadMetadataFile(metadataFile);

                    // 根据元数据信息决定恢复模式
                    bool isPPTMode = metadata.ContainsKey("模式") && metadata["模式"].Contains("PPT放映");
                    bool isWhiteboardMode = metadata.ContainsKey("模式") && metadata["模式"].Contains(FloatingBarStrings.FloatingBar_Whiteboard);

                    // 检查当前是否处于PPT模式
                    bool isCurrentlyInPPTMode = IsInPPTPresentationMode && pptApplication != null;

                    // 检查当前是否处于白板模式
                    bool isCurrentlyInWhiteboardMode = currentMode != 0;

                    // 严格模式隔离：只在对应模式下恢复对应墨迹
                    if (isPPTMode && isCurrentlyInPPTMode)
                    {
                        // 只在PPT放映模式下恢复PPT墨迹
                        RestorePPTStrokesFromZip(tempDir, metadata);
                    }
                    else if (isWhiteboardMode && isCurrentlyInWhiteboardMode)
                    {
                        // 只在白板模式下恢复白板墨迹
                        RestoreWhiteboardStrokesFromZip(tempDir, metadata);
                    }
                    else
                    {
                        // 模式不匹配时，显示提示信息
                        string savedMode = isPPTMode ? "PPT放映" : (isWhiteboardMode ? FloatingBarStrings.FloatingBar_Whiteboard : "未知");
                        string currentMode = isCurrentlyInPPTMode ? "PPT放映" : (isCurrentlyInWhiteboardMode ? FloatingBarStrings.FloatingBar_Whiteboard : "桌面");
                        ShowNotification(string.Format(MainWindowStrings.Main_Strokes_ModeMismatch, savedMode, currentMode));
                        LogHelper.WriteLogToFile($"模式不匹配：保存模式={savedMode}，当前模式={currentMode}", LogHelper.LogType.Warning);
                    }

                    ShowNotification(string.Format(MainWindowStrings.Main_Strokes_OpenIccSuccess, metadata.ContainsKey("总页数") ? metadata["总页数"] : "0"));
                }
                finally
                {
                    // 清理临时目录
                    try
                    {
                        if (Directory.Exists(tempDir))
                            Directory.Delete(tempDir, true);
                    }
                    catch (Exception ex)
                    {
                        LogHelper.WriteLogToFile($"清理临时目录失败: {ex}", LogHelper.LogType.Warning);
                    }
                }
            }
            catch (Exception ex)
            {
                LogHelper.WriteLogToFile($"打开ICC压缩包失败: {ex}", LogHelper.LogType.Error);
                throw;
            }
        }

        /// <summary>
        /// 读取元数据文件
        /// </summary>
        private Dictionary<string, string> ReadMetadataFile(string metadataPath)
        {
            var metadata = new Dictionary<string, string>();

            using (var reader = new StreamReader(metadataPath, Encoding.UTF8))
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    if (line.Contains(":"))
                    {
                        var parts = line.Split(new[] { ':' }, 2);
                        if (parts.Length == 2)
                        {
                            metadata[parts[0].Trim()] = parts[1].Trim();
                        }
                    }
                }
            }

            return metadata;
        }

        /// <summary>
        /// 从ZIP文件恢复PPT墨迹
        /// </summary>
        private void RestorePPTStrokesFromZip(string tempDir, Dictionary<string, string> metadata)
        {
            if (TryBlockFrozenPageMutation("恢复墨迹文件")) return;
            try
            {
                // 确保当前处于PPT放映模式
                if (!IsInPPTPresentationMode || pptApplication == null)
                {
                    throw new InvalidOperationException("当前不在PPT放映模式，无法恢复PPT墨迹");
                }

                // 检查PPT文件路径是否匹配
                if (metadata.ContainsKey("PPT文件路径"))
                {
                    string savedPPTPath = metadata["PPT文件路径"];
                    string currentPPTPath = pptApplication.SlideShowWindows[1].Presentation.FullName;

                    if (!string.IsNullOrEmpty(savedPPTPath) && !string.IsNullOrEmpty(currentPPTPath))
                    {
                        // 使用文件路径哈希值进行比较，避免路径格式差异
                        string savedHash = HashHelper.GetFileHash(savedPPTPath);
                        string currentHash = HashHelper.GetFileHash(currentPPTPath);

                        if (savedHash != currentHash)
                        {
                            throw new InvalidOperationException($"墨迹文件与当前PPT文件不匹配。保存的PPT: {savedPPTPath}，当前PPT: {currentPPTPath}");
                        }
                    }
                }

                // 先把所有页面的墨迹全部解析进内存，再清空原画布。
                // 一旦循环中任意一页解析失败抛出，原画布墨迹不会被销毁、撤销历史可恢复。
                // TimeMachineHistories 容量固定为 101（MW_BoardControls.cs:47），对超出范围或负数页码直接跳过。
                var parsedStrokesByPage = new Dictionary<int, StrokeCollection>();
                var icstkFiles = Directory.GetFiles(tempDir, "page_*.icstk");
                var xmlFiles = Directory.GetFiles(tempDir, "page_*.xml");
                var allFiles = new List<string>();
                allFiles.AddRange(icstkFiles);
                allFiles.AddRange(xmlFiles);

                foreach (var file in allFiles)
                {
                    var fileName = Path.GetFileNameWithoutExtension(file);
                    if (!fileName.StartsWith("page_") || !int.TryParse(fileName.Substring(5), out int pageNumber))
                        continue;
                    if (pageNumber < 1 || pageNumber >= TimeMachineHistories.Length)
                    {
                        LogHelper.WriteLogToFile($"跳过非法页码 {pageNumber}（文件 {file}）", LogHelper.LogType.Warning);
                        continue;
                    }

                    StrokeCollection strokes;
                    string extension = Path.GetExtension(file).ToLower();

                    if (extension == ".xml")
                    {
                        strokes = LoadStrokesFromXML(file);
                    }
                    else
                    {
                        using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read))
                        {
                            strokes = new StrokeCollection(fs);
                        }
                    }

                    if (strokes != null && strokes.Count > 0)
                        parsedStrokesByPage[pageNumber] = strokes;
                }

                // 清空当前墨迹
                ClearStrokes(true);
                timeMachine.ClearStrokeHistory();

                // 重置PPT墨迹存储
                _singlePPTInkManager?.ClearAllStrokes();

                foreach (var pair in parsedStrokesByPage)
                    _singlePPTInkManager?.ForceSaveSlideStrokes(pair.Key, pair.Value);

                // 恢复当前页面的墨迹
                if (_pptManager?.IsInSlideShow == true)
                {
                    int currentSlide = _pptManager.GetCurrentSlideNumber();
                    var currentStrokes = _singlePPTInkManager?.LoadSlideStrokes(currentSlide);
                    if (currentStrokes != null && currentStrokes.Count > 0)
                    {
                        inkCanvas.Strokes.Add(currentStrokes);
                    }
                }

                LogHelper.WriteLogToFile($"成功恢复PPT墨迹，共{allFiles.Count}页");
            }
            catch (Exception ex)
            {
                LogHelper.WriteLogToFile($"恢复PPT墨迹失败: {ex}", LogHelper.LogType.Error);
                throw;
            }
        }

        /// <summary>
        /// 从ZIP文件恢复白板墨迹
        /// </summary>
        private void RestoreWhiteboardStrokesFromZip(string tempDir, Dictionary<string, string> metadata)
        {
            if (TryBlockFrozenPageMutation("恢复墨迹文件")) return;
            try
            {
                // 确保当前处于白板模式
                if (currentMode == 0)
                {
                    throw new InvalidOperationException("当前不在白板模式，无法恢复白板墨迹");
                }

                // 先把全部墨迹解析进内存，循环结束后才清空原画布——解析失败时原墨迹仍存在。
                // 同时把 pageNumber 限定在 TimeMachineHistories 容量（101）内，避免 IndexOutOfRangeException。
                var parsedHistoriesByPage = new Dictionary<int, TimeMachineHistory[]>();
                var files = Directory.GetFiles(tempDir, "page_*.icstk");
                foreach (var file in files)
                {
                    var fileName = Path.GetFileNameWithoutExtension(file);
                    if (!fileName.StartsWith("page_") || !int.TryParse(fileName.Substring(5), out int pageNumber))
                        continue;
                    if (pageNumber < 1 || pageNumber >= TimeMachineHistories.Length)
                    {
                        LogHelper.WriteLogToFile($"跳过非法页码 {pageNumber}（文件 {file}）", LogHelper.LogType.Warning);
                        continue;
                    }

                    using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read))
                    {
                        var strokes = new StrokeCollection(fs);
                        if (strokes.Count > 0)
                        {
                            var history = new TimeMachineHistory(strokes, TimeMachineHistoryType.UserInput, false);
                            parsedHistoriesByPage[pageNumber] = new[] { history };
                        }
                    }
                }

                // 清空当前墨迹
                ClearStrokes(true);
                timeMachine.ClearStrokeHistory();

                // 读取总页数
                int totalPages = 1;
                if (metadata.ContainsKey("总页数") && int.TryParse(metadata["总页数"], out int parsedPages))
                {
                    totalPages = parsedPages;
                }

                // 重置白板状态
                WhiteboardTotalCount = totalPages;
                CurrentWhiteboardIndex = 1;
                ResetInkFreezePageStates();

                // 清空历史记录
                for (int i = 0; i < TimeMachineHistories.Length; i++)
                {
                    TimeMachineHistories[i] = null;
                }

                foreach (var pair in parsedHistoriesByPage)
                    TimeMachineHistories[pair.Key] = pair.Value;

                // 恢复第一页的墨迹
                if (TimeMachineHistories[1] != null)
                {
                    RestoreStrokes();
                }

                // 更新UI显示
                UpdateIndexInfoDisplay();

                LogHelper.WriteLogToFile($"成功恢复白板墨迹，共{totalPages}页");
            }
            catch (Exception ex)
            {
                LogHelper.WriteLogToFile($"恢复白板墨迹失败: {ex}", LogHelper.LogType.Error);
                throw;
            }
        }

        /// <summary>
        /// 打开XML格式的墨迹文件
        /// </summary>
        public void OpenXMLStrokeFile(string filePath)
        {
            if (TryBlockFrozenPageMutation("打开墨迹文件")) return;
            try
            {
                XDocument doc = XDocument.Load(filePath);
                var root = doc.Root;
                if (root == null || root.Name != "InkCanvasStrokes")
                {
                    throw new Exception("无效的XML墨迹文件格式");
                }

                var strokes = new StrokeCollection();
                foreach (var strokeElement in root.Elements("Stroke"))
                {
                    var drawingAttributesStr = strokeElement.Attribute("DrawingAttributes")?.Value ?? "";
                    var da = ParseDrawingAttributes(drawingAttributesStr);

                    var stylusPoints = new StylusPointCollection();
                    var stylusPointsElement = strokeElement.Element("StylusPoints");
                    if (stylusPointsElement != null)
                    {
                        foreach (var pointElement in stylusPointsElement.Elements("StylusPoint"))
                        {
                            double x = double.Parse(pointElement.Attribute("X")?.Value ?? "0");
                            double y = double.Parse(pointElement.Attribute("Y")?.Value ?? "0");
                            float pressure = float.Parse(pointElement.Attribute("PressureFactor")?.Value ?? "0.5");
                            stylusPoints.Add(new StylusPoint(x, y, pressure));
                        }
                    }

                    if (stylusPoints.Count > 0)
                    {
                        var stroke = new Stroke(stylusPoints) { DrawingAttributes = da };
                        strokes.Add(stroke);
                    }
                }

                ClearStrokes(true);
                timeMachine.ClearStrokeHistory();
                inkCanvas.Strokes.Add(strokes);
                LogHelper.NewLog($"XML Strokes Insert: Strokes Count: {inkCanvas.Strokes.Count}");

                // 恢复元素信息
                var elementsFile = Path.ChangeExtension(filePath, ".elements.json");
                if (File.Exists(elementsFile))
                {
                    var elementInfos = JsonConvert.DeserializeObject<List<CanvasElementInfo>>(File.ReadAllText(elementsFile));
                    foreach (var info in elementInfos)
                    {
                        if (info.Type == "Image" && File.Exists(info.SourcePath))
                        {
                            var bitmapImage = new BitmapImage();
                            bitmapImage.BeginInit();
                            bitmapImage.UriSource = new Uri(info.SourcePath);
                            bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
                            bitmapImage.EndInit();
                            bitmapImage.Freeze();
                            var img = new Image
                            {
                                Source = bitmapImage,
                                Width = info.Width,
                                Height = info.Height,
                                Stretch = Enum.TryParse<Stretch>(info.Stretch, out var stretch) ? stretch : Stretch.Fill
                            };
                            InkCanvas.SetLeft(img, info.Left);
                            InkCanvas.SetTop(img, info.Top);
                            inkCanvas.Children.Add(img);
                        }
                        else if (string.Equals(info.Type, "Pdf", StringComparison.OrdinalIgnoreCase) && File.Exists(info.SourcePath))
                        {
                            Dispatcher.BeginInvoke(new Action(() => { _ = RestorePdfFromElementInfoAsync(info); }), DispatcherPriority.Loaded);
                        }
                        else if (string.Equals(info.Type, "Media", StringComparison.OrdinalIgnoreCase) && File.Exists(info.SourcePath))
                        {
                            RestoreMediaFromElementInfo(info);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogHelper.WriteLogToFile($"打开XML墨迹文件失败: {ex}", LogHelper.LogType.Error);
                throw;
            }
        }

        /// <summary>
        /// 从XML文件加载StrokeCollection（辅助方法，用于ZIP文件恢复）
        /// </summary>
        private StrokeCollection LoadStrokesFromXML(string xmlPath)
        {
            try
            {
                XDocument doc = XDocument.Load(xmlPath);
                var root = doc.Root;
                if (root == null || root.Name != "InkCanvasStrokes")
                {
                    return new StrokeCollection();
                }

                var strokes = new StrokeCollection();
                foreach (var strokeElement in root.Elements("Stroke"))
                {
                    var drawingAttributesStr = strokeElement.Attribute("DrawingAttributes")?.Value ?? "";
                    var da = ParseDrawingAttributes(drawingAttributesStr);

                    var stylusPoints = new StylusPointCollection();
                    var stylusPointsElement = strokeElement.Element("StylusPoints");
                    if (stylusPointsElement != null)
                    {
                        foreach (var pointElement in stylusPointsElement.Elements("StylusPoint"))
                        {
                            double x = double.Parse(pointElement.Attribute("X")?.Value ?? "0");
                            double y = double.Parse(pointElement.Attribute("Y")?.Value ?? "0");
                            float pressure = float.Parse(pointElement.Attribute("PressureFactor")?.Value ?? "0.5");
                            stylusPoints.Add(new StylusPoint(x, y, pressure));
                        }
                    }

                    if (stylusPoints.Count > 0)
                    {
                        var stroke = new Stroke(stylusPoints) { DrawingAttributes = da };
                        strokes.Add(stroke);
                    }
                }

                return strokes;
            }
            catch (Exception ex)
            {
                LogHelper.WriteLogToFile($"从XML加载墨迹失败: {ex}", LogHelper.LogType.Error);
                return new StrokeCollection();
            }
        }

        /// <summary>
        /// 从字符串解析DrawingAttributes
        /// </summary>
        private DrawingAttributes ParseDrawingAttributes(string attributesStr)
        {
            var da = new DrawingAttributes();
            var parts = attributesStr.Split(';');
            foreach (var part in parts)
            {
                var kv = part.Split('=');
                if (kv.Length == 2)
                {
                    var key = kv[0].Trim();
                    var value = kv[1].Trim();
                    switch (key)
                    {
                        case "Color":
                            da.Color = (System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString(value);
                            break;
                        case "Width":
                            da.Width = double.Parse(value);
                            break;
                        case "Height":
                            da.Height = double.Parse(value);
                            break;
                        case "FitToCurve":
                            da.FitToCurve = bool.Parse(value);
                            break;
                        case "IsHighlighter":
                            da.IsHighlighter = bool.Parse(value);
                            break;
                        case "IgnorePressure":
                            da.IgnorePressure = bool.Parse(value);
                            break;
                        case "StylusTip":
                            da.StylusTip = Enum.TryParse<StylusTip>(value, out var tip) ? tip : StylusTip.Ellipse;
                            break;
                    }
                }
            }
            return da;
        }

        /// <summary>
        /// 打开单个墨迹文件
        /// </summary>
        /// <param name="filePath">墨迹文件的路径</param>
        /// <remarks>
        /// 该方法会：
        /// 1. 打开墨迹文件并加载墨迹
        /// 2. 检查文件是否包含墨迹
        /// 3. 如果包含墨迹，清空当前墨迹并添加新墨迹
        /// 4. 恢复元素信息
        /// 5. 如果文件流中没有墨迹，尝试从内存流中加载
        /// </remarks>
        public void OpenSingleStrokeFile(string filePath)
        {
            if (TryBlockFrozenPageMutation("打开墨迹文件")) return;
            var fileStreamHasNoStroke = false;
            using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
            {
                var strokes = new StrokeCollection(fs);
                fileStreamHasNoStroke = strokes.Count == 0;
                if (!fileStreamHasNoStroke)
                {
                    ClearStrokes(true);
                    timeMachine.ClearStrokeHistory();
                    inkCanvas.Strokes.Add(strokes);
                    LogHelper.NewLog($"Strokes Insert: Strokes Count: {inkCanvas.Strokes.Count.ToString()}");
                }
            }

            // 恢复元素信息
            var elementsFile = Path.ChangeExtension(filePath, ".elements.json");
            if (File.Exists(elementsFile))
            {
                var elementInfos = JsonConvert.DeserializeObject<List<CanvasElementInfo>>(File.ReadAllText(elementsFile));
                foreach (var info in elementInfos)
                {
                    if (info.Type == "Image" && File.Exists(info.SourcePath))
                    {
                        var bitmapImage = new BitmapImage();
                        bitmapImage.BeginInit();
                        bitmapImage.UriSource = new Uri(info.SourcePath);
                        bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
                        bitmapImage.EndInit();
                        bitmapImage.Freeze();
                        var img = new Image
                        {
                            Source = bitmapImage,
                            Width = info.Width,
                            Height = info.Height,
                            Stretch = Enum.TryParse<Stretch>(info.Stretch, out var stretch) ? stretch : Stretch.Fill
                        };
                        InkCanvas.SetLeft(img, info.Left);
                        InkCanvas.SetTop(img, info.Top);
                        inkCanvas.Children.Add(img);
                    }
                    else if (string.Equals(info.Type, "Pdf", StringComparison.OrdinalIgnoreCase) && File.Exists(info.SourcePath))
                    {
                        Dispatcher.BeginInvoke(new Action(() => { _ = RestorePdfFromElementInfoAsync(info); }), DispatcherPriority.Loaded);
                    }
                    else if (string.Equals(info.Type, "Media", StringComparison.OrdinalIgnoreCase) && File.Exists(info.SourcePath))
                    {
                        RestoreMediaFromElementInfo(info);
                    }
                }
            }

            if (fileStreamHasNoStroke)
                using (var ms = new MemoryStream(File.ReadAllBytes(filePath)))
                {
                    ms.Seek(0, SeekOrigin.Begin);
                    var strokes = new StrokeCollection(ms);
                    ClearStrokes(true);
                    timeMachine.ClearStrokeHistory();
                    inkCanvas.Strokes.Add(strokes);
                    LogHelper.NewLog($"Strokes Insert (2): Strokes Count: {strokes.Count.ToString()}");
                }
        }
    }
}

