using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using PCL.Core.Utils.Exts;
namespace PCL.Core.App.Configuration;
///
/// 配置项。
///
/// 值类型
public class ConfigItem(
string key,
Func defaultValue,
ConfigSource source
) : IConfigScope, ConfigItem
{
public string Key { get; } = key;
public ConfigSource Source { get; } = source;
public Type Type => typeof(TValue);
private Func? _defaultValueConstructor = defaultValue;
private TValue? _defaultValue;
private bool _defaultValueHasSet = false;
#region 默认值逻辑
private TValue _GetDefaultValue()
{
if (_defaultValueHasSet) return _defaultValue!;
_defaultValue = _defaultValueConstructor!();
_defaultValueHasSet = true;
_defaultValueConstructor = null;
return _defaultValue;
}
///
/// 默认值。
///
public TValue DefaultValue => _GetDefaultValue();
public object DefaultValueNoType => DefaultValue ?? default!;
#endregion
public ConfigItem(string key, TValue defaultValue, ConfigSource source)
: this(key, () => defaultValue, source) { }
public IEnumerable CheckScope(IReadOnlySet keys) => keys.Contains(Key) ? [Key] : [];
#region 值获取和修改
private IConfigProvider _Provider { get => field ??= ConfigService.GetProvider(Source); } = null!;
private ConfigValueCache _valueCache = new();
///
/// 指定是否启用缓存。
/// NOTE: 禁用缓存将造成一些功能(如自动监听内容更改)不按预期工作,请仅在真正需要的时候禁用。
///
public bool EnableCache
{
get;
set
{
if (!field) _valueCache.InvalidateAll();
field = value;
}
} = true;
///
/// 处理看起来是新的值,并返回是否真的是新的。
/// 只有启用缓存时该方法才会生效,未启用缓存将始终直接返回 。
///
private bool _ProcessNewCache(TValue newCache, object? argument, bool force = false)
{
if (!EnableCache) return true;
if (!force)
{
// 判断是否是新值
var existsOld = _valueCache.TryRead(out var oldCache, argument);
if (existsOld && EqualityComparer.Default.Equals(oldCache, newCache)) return false;
}
// 对新缓存值执行准备工作
if (newCache is INotifyPropertyChanged reactive)
reactive.PropertyChanged += (_, _) => OnContentChanged();
else if (newCache is INotifyCollectionChanged reactiveCollection)
reactiveCollection.CollectionChanged += (_, _) => OnContentChanged();
// 写入缓存
_valueCache.Write(newCache, argument);
return true;
void OnContentChanged() => SetValue(newCache, argument, bypassCache: true);
}
///
/// 获取配置值。
///
/// 上下文参数
/// 已设置的配置值或默认值
public TValue GetValue(object? argument = null)
{
TValue? value = default; // 这个初始化是多余的,但是煞笔巨硬不初始化会报错
var exists = EnableCache && _valueCache.TryRead(out value, argument);
var newValue = false;
if (!exists)
{
newValue = true;
exists = _Provider.GetValue(Key, out value, argument);
}
var e = _TriggerEvent(ConfigEvent.Get, argument, value, true);
if (e is not null)
{
if (e.Cancelled) return DefaultValue;
if (e.NewValueReplacement is not null) return (TValue)e.NewValueReplacement;
}
if (!exists) value = DefaultValue;
if (newValue) _ProcessNewCache(value!, argument);
return value!;
}
public object GetValueNoType(object? argument = null)
{
return GetValue(argument) ?? default!;
}
///
/// 设置配置值。
///
/// 用于设置的值
/// 上下文参数
/// 强制将传入的值视为新值,不检查缓存,仅在 为 时生效
/// 跳过缓存检查和写入,相当于对本次操作临时将 设为
/// 是否成功设置值,若成功则为 true
public bool SetValue(TValue value, object? argument = null, bool forceNewValue = false, bool bypassCache = false)
{
var e = _TriggerEvent(ConfigEvent.Set, argument, value, isPreview: true);
if (e is not null)
{
if (e.Cancelled) return false;
if (e.NewValueReplacement is not null) value = (TValue)e.NewValueReplacement;
}
if (bypassCache || _ProcessNewCache(value, argument, forceNewValue))
_Provider.SetValue(Key, value, argument);
_TriggerEvent(ConfigEvent.Set, argument, value, e: e, isPreview: false);
return true;
}
public bool SetValueNoType(object value, object? argument = null)
{
try
{
return SetValue((TValue)value, argument);
}
catch (InvalidCastException)
{
// 兼容龙猫妙妙小代码直接传入 string 值的行为
if (value is string v) return SetValue(v.Convert()!, argument);
var msg = $"Value convert failed (required: {Type.FullName}, provided: {value.GetType().FullName})";
throw new InvalidCastException(msg);
}
}
public bool SetDefaultValue(object? argument = null, bool? forceNewValue = null)
{
return SetValue(DefaultValue, argument, forceNewValue ?? IsDefault(argument));
}
public bool Reset(object? argument = null)
{
var e = _TriggerEvent(ConfigEvent.Reset, argument, null, isPreview: true);
if (e is { Cancelled: true }) return false;
_Provider.Delete(Key, argument);
if (EnableCache) _valueCache.Invalidate(argument);
_TriggerEvent(ConfigEvent.Reset, argument, DefaultValueNoType, isPreview: false);
return true;
}
public bool IsDefault(object? argument = null)
{
var result = !_Provider.Exists(Key, argument);
var e = _TriggerEvent(ConfigEvent.CheckDefault, argument, result);
if (e is { NewValueReplacement: not null }) result = (bool)e.NewValueReplacement;
return result;
}
#endregion
#region 事件处理
private readonly HashSet _observers = [];
private readonly HashSet _previewObservers = [];
public void Observe(ConfigObserver observer)
{
if (observer.IsPreview) _previewObservers.Add(observer);
else _observers.Add(observer);
}
public bool Unobserve(ConfigObserver observer)
=> observer.IsPreview ? _previewObservers.Remove(observer) : _observers.Remove(observer);
// 获取值,若未设置则返回 null
private object? _GetValueOrNull(object? argument)
{
var exists = _Provider.GetValue(Key, out var value, argument);
return exists ? value : null;
}
public ConfigEventArgs? TriggerEvent(
ConfigEvent trigger, object? argument,
bool bypassOldValue = false, bool fillNewValue = false)
{
return _TriggerEvent(trigger, argument, null, bypassOldValue, fillNewValue);
}
private ConfigEventArgs? _TriggerEvent(
ConfigEvent trigger, object? argument, object? newValue,
bool bypassOldValue = false, bool fillNewValue = false,
ConfigEventArgs? e = null, bool? isPreview = null)
{
var replaceNewValue = false;
foreach (var observer in (
from observer in (isPreview is { } p ? (p ? _previewObservers : _observers) : _previewObservers.Concat(_observers))
let logic = (int)observer.Event & (int)trigger
where logic > 0
select observer
)) {
if (e is null)
{
if (isPreview == false && !bypassOldValue) bypassOldValue = true;
var currentValue = (fillNewValue || !bypassOldValue) ? _GetValueOrNull(argument) : null;
if (newValue is null && fillNewValue) newValue = currentValue ?? DefaultValue;
e = new ConfigEventArgs(this, trigger, argument, bypassOldValue ? null : currentValue, newValue);
}
observer.Handler(e);
// 对 preview 的特殊处理
if (observer.IsPreview)
{
if (e.NewValueReplacement is not null) replaceNewValue = true; // 记录替换操作
if (e.Cancelled) return e;
}
// 防止非 preview 事件传递替换值
else if (!replaceNewValue && e.NewValueReplacement is not null) e.NewValueReplacement = null;
}
// 防止非 preview 事件传递取消状态
if (e is { Cancelled: true }) e.Cancelled = false;
return e;
}
#endregion
}
///
/// 的非泛型方法抽象层,用于手动解决巨硬
/// 2025 年仍未支持的极其先进的隐式去泛型化。
///
// ReSharper disable once InconsistentNaming
public interface ConfigItem
{
///
/// 配置键。
///
public string Key { get; }
///
/// 配置来源。
///
public ConfigSource Source { get; }
///
/// 配置的 CLR 类型。
///
public Type Type { get; }
///
/// 传入事件观察器以观察事件。
///
public void Observe(ConfigObserver observer);
///
/// 取消观察事件。
///
public bool Unobserve(ConfigObserver observer);
///
/// 触发配置项事件。
///
/// 触发事件
/// 上下文参数
/// 若为 true 则向事件参数的旧值传递 null,否则传递当前值
/// 若为 true,当新值为 null 时将传递当前值或默认值
///
public ConfigEventArgs? TriggerEvent(
ConfigEvent trigger,
object? argument,
bool bypassOldValue = false,
bool fillNewValue = false
);
///
/// 传入事件类型与处理委托以观察事件。
///
public ConfigObserver Observe(ConfigEvent trigger, ConfigEventHandler handler, bool isPreview = false)
{
var observer = new ConfigObserver(trigger, handler, isPreview);
Observe(observer);
return observer;
}
///
/// 传统的用于兼容的值改变事件。
/// 请尽可能避免使用,而是使用
/// 来声明事件观察,或使用 和
/// 来灵活管理事件。
///
public event ConfigEventHandler Changed
{
add => Observe(ConfigEvent.Changed, value);
remove => throw new NotSupportedException("Please use Observe() and Unobserve() to access advanced event management");
}
///
/// 重置配置值,使其变为未设置状态。
///
/// 上下文参数
/// 是否成功重置值,若成功则为 true
public bool Reset(object? argument = null);
///
/// 检查配置值是否为默认值 (未设置状态)
///
/// 上下文参数
public bool IsDefault(object? argument = null);
///
/// 将配置项的值设置为默认值,设置后 将返回 false。
///
/// 上下文参数
/// 强制视为新值,不检查缓存,仅在 为 时生效
/// 是否成功设置值,若成功则为 true
public bool SetDefaultValue(object? argument = null, bool? forceNewValue = null);
///
/// 没有泛型的 。
/// 我们都不想给非引用类型装箱,但是龙猫想。
///
public object GetValueNoType(object? argument = null);
///
/// 没有泛型的 。
/// 我们都不想给非引用类型装箱,但是龙猫想。
///
public bool SetValueNoType(object value, object? argument = null);
///
/// 没有泛型的 。
/// 我们都不想给非引用类型装箱,但是龙猫想。
///
public object DefaultValueNoType { get; }
///
/// 是否启用值缓存,默认为 true。设为 false 将清除已存在的缓存。
///
public bool EnableCache { get; set; }
}