using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Windows.Media.Animation;
namespace Melskin.Appearance
{
///
/// 统一主题管理(合并原 ThemeManager + AppearanceManager 功能)。
/// 功能点:
/// 1. 维护当前明暗模式 / 调色板缓存与事件
/// 2. 提供异步/同步切换(含可选动画)
/// 3. 动画策略:先交换字典(保证新增键立即可用)再对所有可动画 SolidColorBrush 做颜色补间(含新增键过渡)
/// 4. 只允许同时存在一个主题字典 + 一个调色板字典(顺序:主题 -> 调色板 -> 其它)
/// 5. 可选持久化
/// 兼容:保留旧 API (ApplyThemeMode / ApplyThemePalette / SwitchThemeMode 等)
///
/// 设计要点:
/// - “主题”与“调色板”被拆成两个 ResourceDictionary;顺序控制保证覆盖链正确。
/// - 动画采用“字典快速替换 + 颜色补间”而不是实时动态更新键值,确保资源引用立即可见且减少闪烁。
/// - AnimatableBrushKeys 只跟踪 SolidColorBrush 键(避免对非 Brush / 冻结 Freezable 执行动画引发异常)。
/// - 使用 Dispatcher 保障在 UI 线程执行所有 WPF 资源操作。
/// - 同步 API 内部通过 DispatcherFrame 等待异步完成(避免外部 async 污染,同时保持阻塞式调用语义)。
///
public class ThemeManager
{
///
/// 获取当前程序集的命名空间名称,该属性返回一个字符串,表示包含ThemeManager类的程序集的名称。
///
///
/// 该属性用于获取应用程序的命名空间,以便在构建资源路径或其他需要引用程序集名称的地方使用。例如,在定位主题字典路径或着色器文件时,`LibraryNamespace`提供了必要的程序集名称信息。
///
public static string LibraryNamespace => Assembly.GetExecutingAssembly().GetName().Name!;
///
/// 获取主题字典的基路径,该路径用于定位应用程序中的主题资源文件。此属性返回一个字符串,表示到主题资源文件夹的包URI。
///
///
/// 该属性通常被用于构建指向具体主题样式或颜色调色板文件的完整路径。例如,它可用于加载位于`Themes/`目录下的不同主题模式(如Light.xaml, Dark.xaml)或颜色调色板文件(如Accents/LightBlue.xaml)。通过使用`ThemesDictionaryPath`作为基础路径,可以方便地访问和应用不同的主题设置。
///
public static string ThemesDictionaryPath => $"pack://application:,,,/{LibraryNamespace};component/Themes/";
private static bool _persistenceEnabled;
///
/// 当应用程序的主题模式发生变化时触发的事件。此事件允许订阅者在主题模式更改时执行特定的操作。
///
///
/// 该事件通常在调用改变主题模式的方法(如ApplyThemeMode或SwitchThemeMode)后被触发。通过注册此事件,开发者可以监听到主题模式的变化,并根据需要更新UI或其他逻辑。
///
public static event Action? ThemeModeChanged;
///
/// 当应用程序的主题调色板发生变化时触发的事件。此事件允许订阅者在主题调色板更改时执行特定操作。
///
///
/// 该事件通常用于通知UI组件或其他依赖于当前主题调色板的服务,以便它们可以相应地更新其外观或行为。每当通过`ApplyTheme`、`ApplyThemePalette`等方法改变主题调色板时,都会触发此事件,并传递新的`ThemePalette`作为参数给所有注册的处理程序。
///
public static event Action? ThemePaletteChanged;
private static readonly IEasingFunction ThemeEasing = new CubicEase { EasingMode = EasingMode.EaseInOut };
#region Animatable Keys
private static readonly object KeyLock = new();
private static List _animatableBrushKeys = [];
private static IReadOnlyList AnimatableBrushKeys
{
get { lock (KeyLock) return [.. _animatableBrushKeys]; }
}
private static void RefreshAnimatableKeysFromCurrentManagedDictionaries(ResourceDictionary root, bool merge = false)
{
var keys = new HashSet(StringComparer.Ordinal);
if (merge)
{
lock (KeyLock)
{
foreach (var k in _animatableBrushKeys)
keys.Add(k);
}
}
foreach (var dict in root.MergedDictionaries.Where(IsManagedThemeDictionary))
{
foreach (var key in dict.Keys.OfType