using System.Globalization;
using System.Linq.Expressions;

using AIStudio.Settings.DataModel;

using Lua;

namespace AIStudio.Settings;

public static partial class ManagedConfiguration
{
    /// <summary>
    /// Attempts to process the configuration settings from a Lua table for enum types.
    /// </summary>
    /// <remarks>
    /// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
    /// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID.
    /// The setting's value is set to the configured value.
    /// </remarks>
    /// <param name="configPluginId">The ID of the related configuration plugin.</param>
    /// <param name="settings">The Lua table containing the settings to process.</param>
    /// <param name="configSelection">The expression to select the configuration class.</param>
    /// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
    /// <param name="dryRun">When true, the method will not apply any changes, but only check if the configuration can be read.</param>
    /// <param name="_">An unused parameter to help with type inference for enum types. You might ignore it when calling the method.</param>
    /// <typeparam name="TClass">The type of the configuration class.</typeparam>
    /// <typeparam name="TValue">The type of the property within the configuration class.</typeparam>
    /// <returns>True when the configuration was successfully processed, otherwise false.</returns>
    public static bool TryProcessConfiguration<TClass, TValue>(
        Expression<Func<Data, TClass>> configSelection,
        Expression<Func<TClass, TValue>> propertyExpression,
        Guid configPluginId,
        LuaTable settings,
        bool dryRun,
        TValue? _ = default)
        where TValue : Enum
    {
        //
        // Handle configured enum values
        //

        // Check if that configuration was registered:
        if(!TryGet(configSelection, propertyExpression, out var configMeta))
            return false;

        var successful = false;
        var configuredValue = configMeta.Default;

        // Step 1 -- try to read the Lua value out of the Lua table:
        if(settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredEnumValue))
        {
            // Step 2 -- try to read the Lua value as a string:
            if(configuredEnumValue.TryRead<string>(out var configuredEnumText))
            {
                // Step 3 -- try to parse the string as the enum type:
                if (Enum.TryParse(typeof(TValue), configuredEnumText, true, out var configuredEnum))
                {
                    configuredValue = (TValue)configuredEnum;
                    successful = true;
                }
            }
        }

        if(dryRun)
            return successful;

        var settingName = SettingName(propertyExpression);
        var managedMode = ReadManagedConfigurationMode(propertyExpression, settings);
        return HandleParsedScalarValue(configPluginId, dryRun, successful, configMeta, configuredValue, managedMode, settingName);
    }

    /// <summary>
    /// Attempts to process the configuration settings from a Lua table for ISpanParsable types.
    /// </summary>
    /// <remarks>
    /// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
    /// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID.
    /// The setting's value is set to the configured value.
    /// </remarks>
    /// <param name="configPluginId">The ID of the related configuration plugin.</param>
    /// <param name="settings">The Lua table containing the settings to process.</param>
    /// <param name="configSelection">The expression to select the configuration class.</param>
    /// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
    /// <param name="dryRun">When true, the method will not apply any changes, but only check if the configuration can be read.</param>
    /// <param name="_">An unused parameter to help with type inference. You might ignore it when calling the method.</param>
    /// <typeparam name="TClass">The type of the configuration class.</typeparam>
    /// <typeparam name="TValue">The type of the property within the configuration class.</typeparam>
    /// <returns>True when the configuration was successfully processed, otherwise false.</returns>
    public static bool TryProcessConfiguration<TClass, TValue>(
        Expression<Func<Data, TClass>> configSelection,
        Expression<Func<TClass, TValue>> propertyExpression,
        Guid configPluginId,
        LuaTable settings,
        bool dryRun,
        ISpanParsable<TValue>? _ = null)
        where TValue : struct, ISpanParsable<TValue>
    {
        //
        // Handle configured ISpanParsable values
        //

        // Check if that configuration was registered:
        if(!TryGet(configSelection, propertyExpression, out var configMeta))
            return false;

        var successful = false;
        var configuredValue = configMeta.Default;

        // Step 1 -- try to read the Lua value out of the Lua table:
        if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaValue))
        {
            // Step 2a -- try to read the Lua value as a string:
            if (configuredLuaValue.Type is LuaValueType.String && configuredLuaValue.TryRead<string>(out var configuredLuaValueText))
            {
                // Step 3 -- try to parse the string as the target type:
                if (TValue.TryParse(configuredLuaValueText, CultureInfo.InvariantCulture, out var configuredParsedValue))
                {
                    configuredValue = configuredParsedValue;
                    successful = true;
                }
            }

            // Step 2b -- try to read the Lua value:
            if(configuredLuaValue.TryRead<TValue>(out var configuredLuaValueInstance))
            {
                configuredValue = configuredLuaValueInstance;
                successful = true;
            }
        }

        if(dryRun)
            return successful;

        var settingName = SettingName(propertyExpression);
        var managedMode = ReadManagedConfigurationMode(propertyExpression, settings);
        return HandleParsedScalarValue(configPluginId, dryRun, successful, configMeta, configuredValue, managedMode, settingName);
    }

    /// <summary>
    /// Attempts to process the configuration settings from a Lua table for string values.
    /// </summary>
    /// <remarks>
    /// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
    /// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID.
    /// The setting's value is set to the configured value.
    /// </remarks>
    /// <param name="configPluginId">The ID of the related configuration plugin.</param>
    /// <param name="settings">The Lua table containing the settings to process.</param>
    /// <param name="configSelection">The expression to select the configuration class.</param>
    /// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
    /// <param name="dryRun">When true, the method will not apply any changes, but only check if the configuration can be read.</param>
    /// <typeparam name="TClass">The type of the configuration class.</typeparam>
    /// <returns>True when the configuration was successfully processed, otherwise false.</returns>
    public static bool TryProcessConfiguration<TClass>(
        Expression<Func<Data, TClass>> configSelection,
        Expression<Func<TClass, string>> propertyExpression,
        Guid configPluginId,
        LuaTable settings,
        bool dryRun)
    {
        return TryProcessConfiguration(configSelection, propertyExpression, string.Empty, configPluginId, settings, dryRun);
    }

    /// <summary>
    /// Attempts to process the configuration settings from a Lua table for string values.
    /// </summary>
    /// <remarks>
    /// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
    /// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID.
    /// The setting's value is set to the configured value.
    /// </remarks>
    /// <param name="configuredType">Parameter type of the configuration entry.</param>
    /// <param name="configPluginId">The ID of the related configuration plugin.</param>
    /// <param name="settings">The Lua table containing the settings to process.</param>
    /// <param name="configSelection">The expression to select the configuration class.</param>
    /// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
    /// <param name="dryRun">When true, the method will not apply any changes, but only check if the configuration can be read.</param>
    /// <typeparam name="TClass">The type of the configuration class.</typeparam>
    /// <typeparam name="TDataType">The data type of the configured value.</typeparam>
    /// <returns>True when the configuration was successfully processed, otherwise false.</returns>
    public static bool TryProcessConfiguration<TClass, TDataType>(
        Expression<Func<Data, TClass>> configSelection,
        Expression<Func<TClass, string>> propertyExpression,
        TDataType configuredType,
        Guid configPluginId,
        LuaTable settings,
        bool dryRun)
    {
        //
        // Handle configured string values
        //

        // Check if that configuration was registered:
        if(!TryGet(configSelection, propertyExpression, out var configMeta))
            return false;

        var successful = false;
        var configuredValue = configMeta.Default;

        // Step 1 -- try to read the Lua value out of the Lua table:
        if(settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredTextValue))
        {
            // Step 2 -- try to read the Lua value as a string:
            if(configuredTextValue.TryRead<string>(out var configuredText))
            {
                switch (configuredType)
                {
                    // Case: the read string is a Guid:
                    case Guid:
                        successful = Guid.TryParse(configuredText, out var id);
                        configuredValue = successful ? id.ToString().ToLowerInvariant() : configuredText;
                        break;

                    // Case: the read string is just a string:
                    case string:
                        configuredValue = configuredText;
                        successful = true;
                        break;
                }
            }
        }

        var settingName = SettingName(propertyExpression);
        var managedMode = ReadManagedConfigurationMode(propertyExpression, settings);
        return HandleParsedScalarValue(configPluginId, dryRun, successful, configMeta, configuredValue, managedMode, settingName);
    }

    /// <summary>
    /// Attempts to process the configuration settings from a Lua table for ISpanParsable list types.
    /// </summary>
    /// <remarks>
    /// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
    /// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID.
    /// The setting's value is set to the configured value.
    /// </remarks>
    /// <param name="configPluginId">The ID of the related configuration plugin.</param>
    /// <param name="settings">The Lua table containing the settings to process.</param>
    /// <param name="configSelection">The expression to select the configuration class.</param>
    /// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
    /// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
    /// <param name="_">An unused parameter to help with type inference for ISpanParsable types. You might ignore it when calling the method.</param>
    /// <typeparam name="TClass">The type of the configuration class.</typeparam>
    /// <typeparam name="TValue">The type of the property within the configuration class. It is also the type of the list
    /// elements, which must implement ISpanParsable.</typeparam>
    /// <returns>True when the configuration was successfully processed, otherwise false.</returns>

    // ReSharper disable MethodOverloadWithOptionalParameter
    public static bool TryProcessConfiguration<TClass, TValue>(
        Expression<Func<Data, TClass>> configSelection,
        Expression<Func<TClass, IList<TValue>>> propertyExpression,
        Guid configPluginId,
        LuaTable settings,
        bool dryRun,
        ISpanParsable<TValue>? _ = null)
        where TValue : ISpanParsable<TValue>
    {
        //
        // Handle configured ISpanParsable lists
        //

        // Check if that configuration was registered:
        if (!TryGet(configSelection, propertyExpression, out var configMeta))
            return false;

        var successful = false;
        var configuredValue = configMeta.Default;

        // Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
        if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) &&
            configuredLuaList.Type is LuaValueType.Table &&
            configuredLuaList.TryRead<LuaTable>(out var valueTable))
        {
            // Determine the length of the Lua table and prepare a list to hold the parsed values:
            var len = valueTable.ArrayLength;
            var list = new List<TValue>(len);

            // Iterate over each entry in the Lua table:
            for (var index = 1; index <= len; index++)
            {
                // Retrieve the Lua value at the current index:
                var value = valueTable[index];

                // Step 2a -- try to read the Lua value as a string:
                if (value.Type is LuaValueType.String && value.TryRead<string>(out var configuredLuaValueText))
                {
                    // Step 3 -- try to parse the string as the target type:
                    if (TValue.TryParse(configuredLuaValueText, CultureInfo.InvariantCulture, out var configuredParsedValue))
                        list.Add(configuredParsedValue);
                }

                // Step 2b -- try to read the Lua value:
                if (value.TryRead<TValue>(out var configuredLuaValueInstance))
                    list.Add(configuredLuaValueInstance);
            }

            configuredValue = list;
            successful = true;
        }

        if (dryRun)
            return successful;

        return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue);
    }

    // ReSharper restore MethodOverloadWithOptionalParameter

    /// <summary>
    /// Attempts to process the configuration settings from a Lua table for enum list types.
    /// </summary>
    /// <remarks>
    /// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
    /// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID.
    /// The setting's value is set to the configured value.
    /// </remarks>
    /// <param name="configPluginId">The ID of the related configuration plugin.</param>
    /// <param name="settings">The Lua table containing the settings to process.</param>
    /// <param name="configSelection">The expression to select the configuration class.</param>
    /// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
    /// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
    /// <typeparam name="TClass">The type of the configuration class.</typeparam>
    /// <typeparam name="TValue">The type of the property within the configuration class. It is also the type of the list
    /// elements, which must be an enum.</typeparam>
    /// <returns>True when the configuration was successfully processed, otherwise false.</returns>
    public static bool TryProcessConfiguration<TClass, TValue>(
        Expression<Func<Data, TClass>> configSelection,
        Expression<Func<TClass, IList<TValue>>> propertyExpression,
        Guid configPluginId,
        LuaTable settings,
        bool dryRun)
        where TValue : Enum
    {
        //
        // Handle configured enum lists
        //

        // Check if that configuration was registered:
        if(!TryGet(configSelection, propertyExpression, out var configMeta))
            return false;

        var successful = false;
        var configuredValue = configMeta.Default;

        // Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
        if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) &&
            configuredLuaList.Type is LuaValueType.Table &&
            configuredLuaList.TryRead<LuaTable>(out var valueTable))
        {
            // Determine the length of the Lua table and prepare a list to hold the parsed values:
            var len = valueTable.ArrayLength;
            var list = new List<TValue>(len);

            // Iterate over each entry in the Lua table:
            for (var index = 1; index <= len; index++)
            {
                // Retrieve the Lua value at the current index:
                var value = valueTable[index];

                // Step 2 -- try to read the Lua value as a string:
                if (value.Type is LuaValueType.String && value.TryRead<string>(out var configuredLuaValueText))
                {
                    // Step 3 -- try to parse the string as the target type:
                    if (Enum.TryParse(typeof(TValue), configuredLuaValueText, true, out var configuredEnum))
                        list.Add((TValue)configuredEnum);
                }
            }

            configuredValue = list;
            successful = true;
        }

        if(dryRun)
            return successful;

        return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue);
    }

    /// <summary>
    /// Attempts to process the configuration settings from a Lua table for string list types.
    /// </summary>
    /// <remarks>
    /// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
    /// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID.
    /// The setting's value is set to the configured value.
    /// </remarks>
    /// <param name="configPluginId">The ID of the related configuration plugin.</param>
    /// <param name="settings">The Lua table containing the settings to process.</param>
    /// <param name="configSelection">The expression to select the configuration class.</param>
    /// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
    /// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
    /// <typeparam name="TClass">The type of the configuration class.</typeparam>
    /// <returns>True when the configuration was successfully processed, otherwise false.</returns>
    public static bool TryProcessConfiguration<TClass>(
        Expression<Func<Data, TClass>> configSelection,
        Expression<Func<TClass, IList<string>>> propertyExpression,
        Guid configPluginId,
        LuaTable settings,
        bool dryRun)
    {
        //
        // Handle configured string lists
        //

        // Check if that configuration was registered:
        if(!TryGet(configSelection, propertyExpression, out var configMeta))
            return false;

        var successful = false;
        var configuredValue = configMeta.Default;

        // Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
        if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) &&
            configuredLuaList.Type is LuaValueType.Table &&
            configuredLuaList.TryRead<LuaTable>(out var valueTable))
        {
            // Determine the length of the Lua table and prepare a list to hold the parsed values:
            var len = valueTable.ArrayLength;
            var list = new List<string>(len);

            // Iterate over each entry in the Lua table:
            for (var index = 1; index <= len; index++)
            {
                // Retrieve the Lua value at the current index:
                var value = valueTable[index];

                // Step 2 -- try to read the Lua value as a string:
                if (value.Type is LuaValueType.String && value.TryRead<string>(out var configuredLuaValueText))
                    list.Add(configuredLuaValueText);
            }

            configuredValue = list;
            successful = true;
        }

        if(dryRun)
            return successful;

        var settingName = SettingName(propertyExpression);
        var managedMode = ReadManagedConfigurationMode(propertyExpression, settings);
        return HandleParsedScalarValue(configPluginId, dryRun, successful, configMeta, configuredValue, managedMode, settingName);
    }

    /// <summary>
    /// Attempts to process the configuration settings from a Lua table for ISpanParsable set types.
    /// </summary>
    /// <remarks>
    /// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
    /// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID.
    /// The setting's value is set to the configured value.
    /// </remarks>
    /// <param name="configPluginId">The ID of the related configuration plugin.</param>
    /// <param name="settings">The Lua table containing the settings to process.</param>
    /// <param name="configSelection">The expression to select the configuration class.</param>
    /// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
    /// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
    /// <param name="_">An unused parameter to help with type inference for ISpanParsable types. You might ignore it when calling the method.</param>
    /// <typeparam name="TClass">The type of the configuration class.</typeparam>
    /// <typeparam name="TValue">The type of the property within the configuration class. It is also the type of the set
    /// elements, which must implement ISpanParsable.</typeparam>
    /// <returns>True when the configuration was successfully processed, otherwise false.</returns>

    // ReSharper disable MethodOverloadWithOptionalParameter
    public static bool TryProcessConfiguration<TClass, TValue>(
        Expression<Func<Data, TClass>> configSelection,
        Expression<Func<TClass, ISet<TValue>>> propertyExpression,
        Guid configPluginId,
        LuaTable settings,
        bool dryRun,
        ISpanParsable<TValue>? _ = null)
        where TValue : ISpanParsable<TValue>
    {
        //
        // Handle configured ISpanParsable sets
        //

        // Check if that configuration was registered:
        if (!TryGet(configSelection, propertyExpression, out var configMeta))
            return false;

        var successful = false;
        var configuredValue = configMeta.Default;

        // Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
        if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) &&
            configuredLuaList.Type is LuaValueType.Table &&
            configuredLuaList.TryRead<LuaTable>(out var valueTable))
        {
            // Determine the length of the Lua table and prepare a set to hold the parsed values:
            var len = valueTable.ArrayLength;
            var set = new HashSet<TValue>(len);

            // Iterate over each entry in the Lua table:
            for (var index = 1; index <= len; index++)
            {
                // Retrieve the Lua value at the current index:
                var value = valueTable[index];

                // Step 2a -- try to read the Lua value as a string:
                if (value.Type is LuaValueType.String && value.TryRead<string>(out var configuredLuaValueText))
                {
                    // Step 3 -- try to parse the string as the target type:
                    if (TValue.TryParse(configuredLuaValueText, CultureInfo.InvariantCulture, out var configuredParsedValue))
                        set.Add(configuredParsedValue);
                }

                // Step 2b -- try to read the Lua value:
                if (value.TryRead<TValue>(out var configuredLuaValueInstance))
                    set.Add(configuredLuaValueInstance);
            }

            configuredValue = set;
            successful = true;
        }

        if (dryRun)
            return successful;

        return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue);
    }

    // ReSharper restore MethodOverloadWithOptionalParameter

    /// <summary>
    /// Attempts to process the configuration settings from a Lua table for enum set types.
    /// </summary>
    /// <remarks>
    /// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
    /// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID.
    /// The setting's value is set to the configured value.
    /// </remarks>
    /// <param name="configPluginId">The ID of the related configuration plugin.</param>
    /// <param name="settings">The Lua table containing the settings to process.</param>
    /// <param name="configSelection">The expression to select the configuration class.</param>
    /// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
    /// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
    /// <typeparam name="TClass">The type of the configuration class.</typeparam>
    /// <typeparam name="TValue">The type of the property within the configuration class. It is also the type of the set
    /// elements, which must be an enum.</typeparam>
    /// <returns>True when the configuration was successfully processed, otherwise false.</returns>
    public static bool TryProcessConfiguration<TClass, TValue>(
        Expression<Func<Data, TClass>> configSelection,
        Expression<Func<TClass, ISet<TValue>>> propertyExpression,
        Guid configPluginId,
        LuaTable settings,
        bool dryRun)
        where TValue : Enum
    {
        //
        // Handle configured enum sets
        //

        // Check if that configuration was registered:
        if(!TryGet(configSelection, propertyExpression, out var configMeta))
            return false;

        var successful = false;
        var configuredValue = configMeta.Default;

        // Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
        if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) &&
            configuredLuaList.Type is LuaValueType.Table &&
            configuredLuaList.TryRead<LuaTable>(out var valueTable))
        {
            // Determine the length of the Lua table and prepare a set to hold the parsed values:
            var len = valueTable.ArrayLength;
            var set = new HashSet<TValue>(len);

            // Iterate over each entry in the Lua table:
            for (var index = 1; index <= len; index++)
            {
                // Retrieve the Lua value at the current index:
                var value = valueTable[index];

                // Step 2 -- try to read the Lua value as a string:
                if (value.Type is LuaValueType.String && value.TryRead<string>(out var configuredLuaValueText))
                {
                    // Step 3 -- try to parse the string as the target type:
                    if (Enum.TryParse(typeof(TValue), configuredLuaValueText, true, out var configuredEnum))
                        set.Add((TValue)configuredEnum);
                }
            }

            configuredValue = set;
            successful = true;
        }

        if(dryRun)
            return successful;

        return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue);
    }

    /// <summary>
    /// Attempts to process additive plugin contributions for enum set settings from a Lua table.
    /// The contributed values are merged into the existing set, and the setting remains unlocked
    /// so users can add additional values.
    /// </summary>
    /// <param name="configPluginId">The ID of the related configuration plugin.</param>
    /// <param name="settings">The Lua table containing the settings to process.</param>
    /// <param name="configSelection">The expression to select the configuration class.</param>
    /// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
    /// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
    /// <typeparam name="TClass">The type of the configuration class.</typeparam>
    /// <typeparam name="TValue">The type of the property within the configuration class. It is also the type of the set
    /// elements, which must be an enum.</typeparam>
    /// <returns>True when the configuration was successfully processed, otherwise false.</returns>
    public static bool TryProcessConfigurationWithPluginContribution<TClass, TValue>(
        Expression<Func<Data, TClass>> configSelection,
        Expression<Func<TClass, ISet<TValue>>> propertyExpression,
        Guid configPluginId,
        LuaTable settings,
        bool dryRun)
        where TValue : Enum
    {
        //
        // Handle configured enum sets (additive merge)
        //

        // Check if that configuration was registered:
        if (!TryGet(configSelection, propertyExpression, out var configMeta))
            return false;

        var successful = false;
        var configuredValue = new HashSet<TValue>();

        // Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
        if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) &&
            configuredLuaList.Type is LuaValueType.Table &&
            configuredLuaList.TryRead<LuaTable>(out var valueTable))
        {
            // Determine the length of the Lua table and prepare a set to hold the parsed values:
            var len = valueTable.ArrayLength;
            var set = new HashSet<TValue>(len);

            // Iterate over each entry in the Lua table:
            for (var index = 1; index <= len; index++)
            {
                // Retrieve the Lua value at the current index:
                var value = valueTable[index];

                // Step 2 -- try to read the Lua value as a string:
                if (value.Type is LuaValueType.String && value.TryRead<string>(out var configuredLuaValueText))
                {
                    // Step 3 -- try to parse the string as the target type:
                    if (Enum.TryParse(typeof(TValue), configuredLuaValueText, true, out var configuredEnum))
                        set.Add((TValue)configuredEnum);
                }
            }

            configuredValue = set;
            successful = true;
        }

        if (dryRun)
            return successful;

        if (successful)
        {
            var configInstance = configSelection.Compile().Invoke(SettingsManagerAccess.ConfigurationData);
            var currentValue = propertyExpression.Compile().Invoke(configInstance);
            var merged = new HashSet<TValue>(currentValue);
            merged.UnionWith(configuredValue);
            configMeta.SetValue(merged);
            configMeta.SetPluginContribution(new HashSet<TValue>(configuredValue), configPluginId);
        }
        else if (configMeta.HasPluginContribution && configMeta.PluginContributionByConfigPluginId == configPluginId)
        {
            configMeta.ClearPluginContribution();
        }

        if (configMeta.IsLocked && configMeta.LockedByConfigPluginId == configPluginId)
            configMeta.UnlockConfiguration();

        return successful;
    }

    /// <summary>
    /// Attempts to process the configuration settings from a Lua table for string set types.
    /// </summary>
    /// <remarks>
    /// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
    /// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID.
    /// The setting's value is set to the configured value.
    /// </remarks>
    /// <param name="configPluginId">The ID of the related configuration plugin.</param>
    /// <param name="settings">The Lua table containing the settings to process.</param>
    /// <param name="configSelection">The expression to select the configuration class.</param>
    /// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
    /// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
    /// <typeparam name="TClass">The type of the configuration class.</typeparam>
    /// <returns>True when the configuration was successfully processed, otherwise false.</returns>
    public static bool TryProcessConfiguration<TClass>(
        Expression<Func<Data, TClass>> configSelection,
        Expression<Func<TClass, ISet<string>>> propertyExpression,
        Guid configPluginId,
        LuaTable settings,
        bool dryRun)
    {
        //
        // Handle configured string sets
        //

        // Check if that configuration was registered:
        if(!TryGet(configSelection, propertyExpression, out var configMeta))
            return false;

        var successful = false;
        var configuredValue = configMeta.Default;

        // Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
        if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) &&
            configuredLuaList.Type is LuaValueType.Table &&
            configuredLuaList.TryRead<LuaTable>(out var valueTable))
        {
            // Determine the length of the Lua table and prepare a set to hold the parsed values:
            var len = valueTable.ArrayLength;
            var set = new HashSet<string>(len);

            // Iterate over each entry in the Lua table:
            for (var index = 1; index <= len; index++)
            {
                // Retrieve the Lua value at the current index:
                var value = valueTable[index];

                // Step 2 -- try to read the Lua value as a string:
                if (value.Type is LuaValueType.String && value.TryRead<string>(out var configuredLuaValueText))
                    set.Add(configuredLuaValueText);
            }

            configuredValue = set;
            successful = true;
        }

        if(dryRun)
            return successful;

        return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue);
    }

    /// <summary>
    /// Attempts to process the configuration settings from a Lua table for string dictionary types.
    /// </summary>
    /// <remarks>
    /// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
    /// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID.
    /// The setting's value is set to the configured value.
    /// </remarks>
    /// <param name="configPluginId">The ID of the related configuration plugin.</param>
    /// <param name="settings">The Lua table containing the settings to process.</param>
    /// <param name="configSelection">The expression to select the configuration class.</param>
    /// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
    /// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
    /// <typeparam name="TClass">The type of the configuration class.</typeparam>
    /// <returns>True when the configuration was successfully processed, otherwise false.</returns>
    public static bool TryProcessConfiguration<TClass>(
        Expression<Func<Data, TClass>> configSelection,
        Expression<Func<TClass, IDictionary<string, string>>> propertyExpression,
        Guid configPluginId,
        LuaTable settings,
        bool dryRun)
    {
        //
        // Handle configured string dictionaries (both keys and values are strings)
        //

        // Check if that configuration was registered:
        if(!TryGet(configSelection, propertyExpression, out var configMeta))
            return false;

        var successful = false;
        var configuredValue = configMeta.Default;

        // Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
        if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) &&
            configuredLuaList.Type is LuaValueType.Table &&
            configuredLuaList.TryRead<LuaTable>(out var valueTable))
        {
            // Determine the length of the Lua table and prepare a dictionary to hold the parsed key-value pairs.
            // Instead of using ArrayLength, we use HashMapCount to get the number of key-value pairs:
            var len = valueTable.HashMapCount;
            if (len > 0)
                configuredValue.Clear();

            // In order to iterate over all key-value pairs in the Lua table, we have to use TryGetNext.
            // Thus, we initialize the previous key variable to Nil and keep calling TryGetNext until
            // there are no more pairs:
            var previousKey = LuaValue.Nil;
            while(valueTable.TryGetNext(previousKey, out var pair))
            {
                // Update the previous key for the next iteration:
                previousKey = pair.Key;

                // Try to read both the key and the value as strings:
                var hadKey = pair.Key.TryRead<string>(out var key);
                var hadValue = pair.Value.TryRead<string>(out var value);

                // If both key and value were read successfully, add them to the dictionary:
                if (hadKey && hadValue)
                    configuredValue[key] = value;
            }

            successful = true;
        }

        if(dryRun)
            return successful;

        return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue);
    }

    /// <summary>
    /// Attempts to process the configuration settings from a Lua table for enum dictionary types.
    /// </summary>
    /// <remarks>
    /// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
    /// Furthermore, it applies the configured managed state to the provided configuration plugin ID.
    /// The setting's value is set to the configured value when locked or when the editable default should apply.
    /// </remarks>
    /// <param name="configPluginId">The ID of the related configuration plugin.</param>
    /// <param name="settings">The Lua table containing the settings to process.</param>
    /// <param name="configSelection">The expression to select the configuration class.</param>
    /// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
    /// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
    /// <typeparam name="TClass">The type of the configuration class.</typeparam>
    /// <typeparam name="TKey">The enum type of the dictionary keys.</typeparam>
    /// <typeparam name="TValue">The enum type of the dictionary values.</typeparam>
    /// <returns>True when the configuration was successfully processed, otherwise false.</returns>
    public static bool TryProcessConfiguration<TClass, TKey, TValue>(
        Expression<Func<Data, TClass>> configSelection,
        Expression<Func<TClass, Dictionary<TKey, TValue>>> propertyExpression,
        Guid configPluginId,
        LuaTable settings,
        bool dryRun)
        where TKey : struct, Enum
        where TValue : struct, Enum
    {
        //
        // Handle configured enum dictionaries (Lua keys and values are strings)
        //

        // Check if that configuration was registered:
        if(!TryGet(configSelection, propertyExpression, out var configMeta))
            return false;

        var successful = false;
        var configuredValue = new Dictionary<TKey, TValue>(configMeta.Default);

        // Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
        if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) &&
            configuredLuaList.Type is LuaValueType.Table &&
            configuredLuaList.TryRead<LuaTable>(out var valueTable))
        {
            configuredValue.Clear();

            // In order to iterate over all key-value pairs in the Lua table, we have to use TryGetNext.
            // Thus, we initialize the previous key variable to Nil and keep calling TryGetNext until
            // there are no more pairs:
            var previousKey = LuaValue.Nil;
            while(valueTable.TryGetNext(previousKey, out var pair))
            {
                // Update the previous key for the next iteration:
                previousKey = pair.Key;

                // Try to read both the key and the value as strings:
                var hadKey = pair.Key.TryRead<string>(out var keyText);
                var hadValue = pair.Value.TryRead<string>(out var valueText);

                // If both key and value were read successfully, parse and add them to the dictionary:
                if (hadKey
                    && hadValue
                    && Enum.TryParse<TKey>(keyText, true, out var key)
                    && Enum.TryParse<TValue>(valueText, true, out var value))
                    configuredValue[key] = value;
            }

            successful = true;
        }

        if(dryRun)
            return successful;

        var settingName = SettingName(propertyExpression);
        var managedMode = ReadManagedConfigurationMode(propertyExpression, settings);
        return HandleParsedScalarValue(configPluginId, dryRun, successful, configMeta, configuredValue, managedMode, settingName);
    }

    /// <summary>
    /// Handles the parsed configuration value based on whether the parsing was successful and whether it's a dry run.
    /// </summary>
    /// <param name="configPluginId">The ID of the related configuration plugin.</param>
    /// <param name="dryRun">When true, no changes will be applied.</param>
    /// <param name="successful">Indicates whether the configuration value was successfully parsed.</param>
    /// <param name="configMeta">The configuration metadata.</param>
    /// <param name="configuredValue">>The parsed configuration value.</param>
    /// <typeparam name="TClass">The type of the configuration class.</typeparam>
    /// <typeparam name="TValue">The type of the configuration property value.</typeparam>
    /// <returns>True when the configuration was successfully processed, otherwise false.</returns>
    private static bool HandleParsedValue<TClass, TValue>(
        Guid configPluginId,
        bool dryRun,
        bool successful,
        ConfigMeta<TClass, TValue> configMeta,
        TValue configuredValue)
    {
        if(dryRun)
            return successful;

        switch (successful)
        {
            case true:
                //
                // Case: the setting was configured, and we could read the value successfully.
                //

                // Set the configured value and lock the configuration:
                configMeta.SetValue(configuredValue);
                configMeta.LockConfiguration(configPluginId);
                break;

            case false when configMeta.IsLocked && configMeta.LockedByConfigPluginId == configPluginId:
                //
                // Case: the setting was configured previously, but we could not read the value successfully.
                // This happens when the setting was removed from the configuration plugin. We handle that
                // case only when the setting was locked and managed by the same configuration plugin.
                //
                // The other case, when the setting was locked and managed by a different configuration plugin,
                // is handled by the CleanupLeftOverManagedConfigurations method, which checks if the configuration
                // plugin is still available. If it is not available, it resets the locked state of the
                // configuration setting, allowing it to be reconfigured by a different plugin or left unchanged.
                //
                configMeta.ResetLockedConfiguration();
                break;

            case false:
                //
                // Case: the setting was not configured, or we could not read the value successfully.
                // We do not change the setting, and it remains at whatever value it had before.
                //
                break;
        }

        return successful;
    }

    private static bool HandleParsedScalarValue<TClass, TValue>(
        Guid configPluginId,
        bool dryRun,
        bool successful,
        ConfigMeta<TClass, TValue> configMeta,
        TValue configuredValue,
        ManagedConfigurationMode managedMode,
        string settingName)
    {
        if (dryRun)
            return successful;

        switch (successful)
        {
            case true when managedMode is ManagedConfigurationMode.LOCKED:
                ClearEditableDefaultState(settingName);
                configMeta.ClearEditableDefaultConfiguration();
                configMeta.SetValue(configuredValue);
                configMeta.LockConfiguration(configPluginId);
                break;

            case true when managedMode is ManagedConfigurationMode.EDITABLE_DEFAULT:
                var currentValueSerialized = SerializeManagedScalarValue(configMeta.GetValue());
                var configuredValueSerialized = SerializeManagedScalarValue(configuredValue);

                string lastAppliedValue;
                if (!TryGetEditableDefaultState(settingName, out var editableDefaultState))
                {
                    configMeta.SetValue(configuredValue);
                    lastAppliedValue = configuredValueSerialized;
                }
                else
                {
                    lastAppliedValue = editableDefaultState.LastAppliedValue;
                    if (string.Equals(currentValueSerialized, lastAppliedValue, StringComparison.Ordinal))
                    {
                        configMeta.SetValue(configuredValue);
                        lastAppliedValue = configuredValueSerialized;
                    }
                }

                SetEditableDefaultState(settingName, configPluginId, lastAppliedValue);
                configMeta.UnlockConfiguration();
                configMeta.SetEditableDefaultConfiguration(configPluginId);
                break;

            case false when configMeta.IsLocked && configMeta.LockedByConfigPluginId == configPluginId:
                configMeta.ResetLockedConfiguration();
                break;

            case false when configMeta.ManagedMode is ManagedConfigurationMode.EDITABLE_DEFAULT
                            && TryGetEditableDefaultState(settingName, out var editableDefaultStateToRemove)
                            && editableDefaultStateToRemove.ConfigPluginId == configPluginId:
                configMeta.ClearEditableDefaultConfiguration();
                ClearEditableDefaultState(settingName);
                break;
        }

        return successful;
    }

    private static ManagedConfigurationMode ReadManagedConfigurationMode<TClass, TValue>(
        Expression<Func<TClass, TValue>> propertyExpression,
        LuaTable settings)
    {
        var allowUserOverrideSettingName = $"{SettingsManager.ToSettingName(propertyExpression)}.AllowUserOverride";
        if (!settings.TryGetValue(allowUserOverrideSettingName, out var allowUserOverrideValue))
            return ManagedConfigurationMode.LOCKED;

        if (allowUserOverrideValue.TryRead<bool>(out var allowUserOverride))
            return allowUserOverride ? ManagedConfigurationMode.EDITABLE_DEFAULT : ManagedConfigurationMode.LOCKED;

        if (allowUserOverrideValue.TryRead<string>(out var allowUserOverrideText) && bool.TryParse(allowUserOverrideText, out allowUserOverride))
            return allowUserOverride ? ManagedConfigurationMode.EDITABLE_DEFAULT : ManagedConfigurationMode.LOCKED;

        return ManagedConfigurationMode.LOCKED;
    }

    private static string SerializeManagedScalarValue<TValue>(TValue value) => value switch
    {
        null => string.Empty,
        string text => text,
        System.Collections.IDictionary dictionary => string.Join(";", dictionary.Keys
            .Cast<object>()
            .OrderBy(key => key.ToString(), StringComparer.Ordinal)
            .Select(key => $"{key}:{dictionary[key]}")),
        System.Collections.IEnumerable enumerable => string.Join(";", enumerable
            .Cast<object>()
            .Select(item => item.ToString() ?? string.Empty)
            .Order(StringComparer.Ordinal)),
        IFormattable formattable => formattable.ToString(null, CultureInfo.InvariantCulture),

        _ => value.ToString() ?? string.Empty,
    };
}