using System.Collections.Concurrent; using System.Linq.Expressions; using AIStudio.Settings.DataModel; using AIStudio.Tools.PluginSystem; namespace AIStudio.Settings; public static partial class ManagedConfiguration { private static readonly ConcurrentDictionary METADATA = new(); /// /// Attempts to retrieve the configuration metadata for a given configuration selection and /// property expression (enum-based). /// /// /// When no configuration metadata is found, it returns a NoConfig instance with the default /// value set to default(TValue). This allows the caller to handle the absence of configuration /// gracefully. In such cases, the return value of the method will be false. /// /// The expression to select the configuration class. /// The expression to select the property within the /// configuration class. /// The output parameter that will hold the configuration metadata /// if found. /// The type of the configuration class. /// The type of the property within the configuration class. /// True if the configuration metadata was found, otherwise false. public static bool TryGet( Expression> configSelection, Expression> propertyExpression, out ConfigMeta configMeta) where TValue : Enum { var configPath = Path(configSelection, propertyExpression); if (METADATA.TryGetValue(configPath, out var value) && value is ConfigMeta meta) { configMeta = meta; return true; } configMeta = new NoConfig(configSelection, propertyExpression) { Default = default!, }; return false; } /// /// Attempts to retrieve the configuration metadata for a given configuration selection and /// property expression (string-based). /// /// /// When no configuration metadata is found, it returns a NoConfig instance with the default /// value set to default(TValue). This allows the caller to handle the absence of configuration /// gracefully. In such cases, the return value of the method will be false. /// /// The expression to select the configuration class. /// The expression to select the property within the /// configuration class. /// The output parameter that will hold the configuration metadata /// if found. /// The type of the configuration class. /// True if the configuration metadata was found, otherwise false. public static bool TryGet( Expression> configSelection, Expression> propertyExpression, out ConfigMeta configMeta) { var configPath = Path(configSelection, propertyExpression); if (METADATA.TryGetValue(configPath, out var value) && value is ConfigMeta meta) { configMeta = meta; return true; } configMeta = new NoConfig(configSelection, propertyExpression) { Default = string.Empty, }; return false; } /// /// Attempts to retrieve the configuration metadata for a given configuration selection and /// property expression (ISpanParsable-based). /// /// /// When no configuration metadata is found, it returns a NoConfig instance with the default /// value set to default(TValue). This allows the caller to handle the absence of configuration /// gracefully. In such cases, the return value of the method will be false. /// /// The expression to select the configuration class. /// The expression to select the property within the /// configuration class. /// The output parameter that will hold the configuration metadata /// if found. /// An optional parameter to help with method overload resolution. /// The type of the configuration class. /// The type of the property within the configuration class. /// True if the configuration metadata was found, otherwise false. // ReSharper disable MethodOverloadWithOptionalParameter public static bool TryGet( Expression> configSelection, Expression> propertyExpression, out ConfigMeta configMeta, ISpanParsable? _ = null) where TValue : struct, ISpanParsable { var configPath = Path(configSelection, propertyExpression); if (METADATA.TryGetValue(configPath, out var value) && value is ConfigMeta meta) { configMeta = meta; return true; } configMeta = new NoConfig(configSelection, propertyExpression) { Default = default!, }; return false; } // ReSharper restore MethodOverloadWithOptionalParameter /// /// Attempts to retrieve the configuration metadata for a list-based setting. /// /// /// When no configuration metadata is found, it returns a NoConfig instance with the default /// value set to an empty list. This allows the caller to handle the absence of configuration /// gracefully. In such cases, the return value of the method will be false. /// /// The expression to select the configuration class. /// The expression to select the property within the /// configuration class. /// The output parameter that will hold the configuration metadata /// if found. /// The type of the configuration class. /// The type of the property within the configuration class. /// True if the configuration metadata was found, otherwise false. public static bool TryGet( Expression> configSelection, Expression>> propertyExpression, out ConfigMeta> configMeta) { var configPath = Path(configSelection, propertyExpression); if (METADATA.TryGetValue(configPath, out var value) && value is ConfigMeta> meta) { configMeta = meta; return true; } configMeta = new NoConfig>(configSelection, propertyExpression) { Default = [], }; return false; } /// /// Attempts to retrieve the configuration metadata for a set-based setting. /// /// /// When no configuration metadata is found, it returns a NoConfig instance with the default /// value set to an empty set. This allows the caller to handle the absence of configuration /// gracefully. In such cases, the return value of the method will be false. /// /// The expression to select the configuration class. /// The expression to select the property within the /// configuration class. /// The output parameter that will hold the configuration metadata /// if found. /// The type of the configuration class. /// The type of the property within the configuration class. /// True if the configuration metadata was found, otherwise false. public static bool TryGet( Expression> configSelection, Expression>> propertyExpression, out ConfigMeta> configMeta) { var configPath = Path(configSelection, propertyExpression); if (METADATA.TryGetValue(configPath, out var value) && value is ConfigMeta> meta) { configMeta = meta; return true; } configMeta = new NoConfig>(configSelection, propertyExpression) { Default = new HashSet(), }; return false; } /// /// Attempts to retrieve the configuration metadata for a string dictionary-based setting. /// /// /// When no configuration metadata is found, it returns a NoConfig instance with the default /// value set to an empty dictionary. This allows the caller to handle the absence of configuration /// gracefully. In such cases, the return value of the method will be false. /// /// The expression to select the configuration class. /// The expression to select the property within the /// configuration class. /// The output parameter that will hold the configuration metadata /// if found. /// The type of the configuration class. /// True if the configuration metadata was found, otherwise false. public static bool TryGet( Expression> configSelection, Expression>> propertyExpression, out ConfigMeta> configMeta) { var configPath = Path(configSelection, propertyExpression); if (METADATA.TryGetValue(configPath, out var value) && value is ConfigMeta> meta) { configMeta = meta; return true; } configMeta = new NoConfig>(configSelection, propertyExpression) { Default = new Dictionary(), }; return false; } /// /// Checks if a configuration setting is left over from a configuration plugin that is no longer available. /// If the configuration setting is locked and managed by a configuration plugin that is not available, /// it resets the managed state of the configuration setting and returns true. /// Otherwise, it returns false. /// /// The expression to select the configuration class. /// The expression to select the property within the configuration class. /// The collection of available plugins to check against. /// The type of the configuration class. /// The type of the property within the configuration class. /// True if the configuration setting is left over and was reset, otherwise false. public static bool IsConfigurationLeftOver( Expression> configSelection, Expression> propertyExpression, IEnumerable availablePlugins) where TValue : Enum { if (!TryGet(configSelection, propertyExpression, out var configMeta)) return false; if (configMeta.MangedByConfigPluginId == Guid.Empty || !configMeta.IsLocked) return false; var plugin = availablePlugins.FirstOrDefault(x => x.Id == configMeta.MangedByConfigPluginId); if (plugin is null) { configMeta.ResetManagedState(); return true; } return false; } public static bool IsConfigurationLeftOver( Expression> configSelection, Expression> propertyExpression, IEnumerable availablePlugins) { if (!TryGet(configSelection, propertyExpression, out var configMeta)) return false; if (configMeta.MangedByConfigPluginId == Guid.Empty || !configMeta.IsLocked) return false; var plugin = availablePlugins.FirstOrDefault(x => x.Id == configMeta.MangedByConfigPluginId); if (plugin is null) { configMeta.ResetManagedState(); return true; } return false; } // ReSharper disable MethodOverloadWithOptionalParameter public static bool IsConfigurationLeftOver( Expression> configSelection, Expression> propertyExpression, IEnumerable availablePlugins, ISpanParsable? _ = null) where TValue : struct, ISpanParsable { if (!TryGet(configSelection, propertyExpression, out var configMeta)) return false; if (configMeta.MangedByConfigPluginId == Guid.Empty || !configMeta.IsLocked) return false; var plugin = availablePlugins.FirstOrDefault(x => x.Id == configMeta.MangedByConfigPluginId); if (plugin is null) { configMeta.ResetManagedState(); return true; } return false; } // ReSharper restore MethodOverloadWithOptionalParameter public static bool IsConfigurationLeftOver( Expression> configSelection, Expression>> propertyExpression, IEnumerable availablePlugins) { if (!TryGet(configSelection, propertyExpression, out var configMeta)) return false; if (configMeta.MangedByConfigPluginId == Guid.Empty || !configMeta.IsLocked) return false; var plugin = availablePlugins.FirstOrDefault(x => x.Id == configMeta.MangedByConfigPluginId); if (plugin is null) { configMeta.ResetManagedState(); return true; } return false; } public static bool IsConfigurationLeftOver( Expression> configSelection, Expression>> propertyExpression, IEnumerable availablePlugins) { if (!TryGet(configSelection, propertyExpression, out var configMeta)) return false; if (configMeta.MangedByConfigPluginId == Guid.Empty || !configMeta.IsLocked) return false; var plugin = availablePlugins.FirstOrDefault(x => x.Id == configMeta.MangedByConfigPluginId); if (plugin is null) { configMeta.ResetManagedState(); return true; } return false; } public static bool IsConfigurationLeftOver( Expression> configSelection, Expression>> propertyExpression, IEnumerable availablePlugins) { if (!TryGet(configSelection, propertyExpression, out var configMeta)) return false; if (configMeta.MangedByConfigPluginId == Guid.Empty || !configMeta.IsLocked) return false; var plugin = availablePlugins.FirstOrDefault(x => x.Id == configMeta.MangedByConfigPluginId); if (plugin is null) { configMeta.ResetManagedState(); return true; } return false; } private static string Path(Expression> configSelection, Expression> propertyExpression) { var className = typeof(TClass).Name; var memberExpressionConfig = configSelection.GetMemberExpression(); var configName = memberExpressionConfig.Member.Name; var memberExpressionProperty = propertyExpression.GetMemberExpression(); var propertyName = memberExpressionProperty.Member.Name; var configPath = $"{configName}.{className}.{propertyName}"; return configPath; } }