using System.Globalization;
using System.Linq.Expressions;
using AIStudio.Settings.DataModel;
using Lua;
namespace AIStudio.Settings;
public static partial class ManagedConfiguration
{
    /// 
    /// Attempts to process the configuration settings from a Lua table for enum types.
    /// 
    /// 
    /// 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.
    /// 
    /// The ID of the related configuration plugin.
    /// The Lua table containing the settings to process.
    /// The expression to select the configuration class.
    /// The expression to select the property within the configuration class.
    /// When true, the method will not apply any changes, but only check if the configuration can be read.
    /// An unused parameter to help with type inference for enum types. You might ignore it when calling the method.
    /// The type of the configuration class.
    /// The type of the property within the configuration class.
    /// True when the configuration was successfully processed, otherwise false.
    public static bool TryProcessConfiguration(
        Expression> configSelection,
        Expression> 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(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;
        
        return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue);
    }
    /// 
    /// Attempts to process the configuration settings from a Lua table for ISpanParsable types.
    /// 
    /// 
    /// 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.
    /// 
    /// The ID of the related configuration plugin.
    /// The Lua table containing the settings to process.
    /// The expression to select the configuration class.
    /// The expression to select the property within the configuration class.
    /// When true, the method will not apply any changes, but only check if the configuration can be read.
    /// An unused parameter to help with type inference. You might ignore it when calling the method.
    /// The type of the configuration class.
    /// The type of the property within the configuration class.
    /// True when the configuration was successfully processed, otherwise false.
    public static bool TryProcessConfiguration(
        Expression> configSelection,
        Expression> propertyExpression,
        Guid configPluginId,
        LuaTable settings,
        bool dryRun,
        ISpanParsable? _ = null)
        where TValue : struct, ISpanParsable
    {
        //
        // 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(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(out var configuredLuaValueInstance))
            {
                configuredValue = configuredLuaValueInstance;
                successful = true;
            }
        }
        if(dryRun)
            return successful;
        
        return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue);
    }
    
    /// 
    /// Attempts to process the configuration settings from a Lua table for string values.
    /// 
    /// 
    /// 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.
    /// 
    /// The ID of the related configuration plugin.
    /// The Lua table containing the settings to process.
    /// The expression to select the configuration class.
    /// The expression to select the property within the configuration class.
    /// When true, the method will not apply any changes, but only check if the configuration can be read.
    /// The type of the configuration class.
    /// True when the configuration was successfully processed, otherwise false.
    public static bool TryProcessConfiguration(
        Expression> configSelection,
        Expression> propertyExpression,
        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(out var configuredText))
            {
                configuredValue = configuredText;
                successful = true;
            }
        }
        
        return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue);
    }
    /// 
    /// Attempts to process the configuration settings from a Lua table for ISpanParsable list types.
    /// 
    /// 
    /// 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.
    /// 
    /// The ID of the related configuration plugin.
    /// The Lua table containing the settings to process.
    /// The expression to select the configuration class.
    /// The expression to select the property within the configuration class.
    /// When true, the method will not apply any changes but only check if the configuration can be read.
    /// An unused parameter to help with type inference for ISpanParsable types. You might ignore it when calling the method.
    /// The type of the configuration class.
    /// The type of the property within the configuration class. It is also the type of the list
    /// elements, which must implement ISpanParsable.
    /// True when the configuration was successfully processed, otherwise false.
    // ReSharper disable MethodOverloadWithOptionalParameter
    public static bool TryProcessConfiguration(
        Expression> configSelection,
        Expression>> propertyExpression,
        Guid configPluginId,
        LuaTable settings,
        bool dryRun,
        ISpanParsable? _ = null)
        where TValue : ISpanParsable
    {
        //
        // 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(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(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(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(out var configuredLuaValueInstance))
                    list.Add(configuredLuaValueInstance);
            }
            configuredValue = list;
            successful = true;
        }
        if (dryRun)
            return successful;
        return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue);
    }
    // ReSharper restore MethodOverloadWithOptionalParameter
    
    /// 
    /// Attempts to process the configuration settings from a Lua table for enum list types.
    /// 
    /// 
    /// 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.
    /// 
    /// The ID of the related configuration plugin.
    /// The Lua table containing the settings to process.
    /// The expression to select the configuration class.
    /// The expression to select the property within the configuration class.
    /// When true, the method will not apply any changes but only check if the configuration can be read.
    /// The type of the configuration class.
    /// The type of the property within the configuration class. It is also the type of the list
    /// elements, which must be an enum.
    /// True when the configuration was successfully processed, otherwise false.
    public static bool TryProcessConfiguration(
        Expression> configSelection,
        Expression>> 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(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(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(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);
    }
    
    /// 
    /// Attempts to process the configuration settings from a Lua table for string list types.
    /// 
    /// 
    /// 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.
    /// 
    /// The ID of the related configuration plugin.
    /// The Lua table containing the settings to process.
    /// The expression to select the configuration class.
    /// The expression to select the property within the configuration class.
    /// When true, the method will not apply any changes but only check if the configuration can be read.
    /// The type of the configuration class.
    /// True when the configuration was successfully processed, otherwise false.
    public static bool TryProcessConfiguration(
        Expression> configSelection,
        Expression>> 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(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(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(out var configuredLuaValueText))
                    list.Add(configuredLuaValueText);
            }
		
            configuredValue = list;
            successful = true;
        }
        
        if(dryRun)
            return successful;
        
        return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue);
    }
    /// 
    /// Attempts to process the configuration settings from a Lua table for ISpanParsable set types.
    /// 
    /// 
    /// 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.
    /// 
    /// The ID of the related configuration plugin.
    /// The Lua table containing the settings to process.
    /// The expression to select the configuration class.
    /// The expression to select the property within the configuration class.
    /// When true, the method will not apply any changes but only check if the configuration can be read.
    /// An unused parameter to help with type inference for ISpanParsable types. You might ignore it when calling the method.
    /// The type of the configuration class.
    /// The type of the property within the configuration class. It is also the type of the set
    /// elements, which must implement ISpanParsable.
    /// True when the configuration was successfully processed, otherwise false.
    // ReSharper disable MethodOverloadWithOptionalParameter
    public static bool TryProcessConfiguration(
        Expression> configSelection,
        Expression>> propertyExpression,
        Guid configPluginId,
        LuaTable settings,
        bool dryRun,
        ISpanParsable? _ = null)
        where TValue : ISpanParsable
    {
        //
        // 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(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(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(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(out var configuredLuaValueInstance))
                    set.Add(configuredLuaValueInstance);
            }
            configuredValue = set;
            successful = true;
        }
        if (dryRun)
            return successful;
        return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue);
    }
    // ReSharper restore MethodOverloadWithOptionalParameter
    
    /// 
    /// Attempts to process the configuration settings from a Lua table for enum set types.
    /// 
    /// 
    /// 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.
    /// 
    /// The ID of the related configuration plugin.
    /// The Lua table containing the settings to process.
    /// The expression to select the configuration class.
    /// The expression to select the property within the configuration class.
    /// When true, the method will not apply any changes but only check if the configuration can be read.
    /// The type of the configuration class.
    /// The type of the property within the configuration class. It is also the type of the set
    /// elements, which must be an enum.
    /// True when the configuration was successfully processed, otherwise false.
    public static bool TryProcessConfiguration(
        Expression> configSelection,
        Expression>> 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(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(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(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);
    }
    
    /// 
    /// Attempts to process the configuration settings from a Lua table for string set types.
    /// 
    /// 
    /// 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.
    /// 
    /// The ID of the related configuration plugin.
    /// The Lua table containing the settings to process.
    /// The expression to select the configuration class.
    /// The expression to select the property within the configuration class.
    /// When true, the method will not apply any changes but only check if the configuration can be read.
    /// The type of the configuration class.
    /// True when the configuration was successfully processed, otherwise false.
    public static bool TryProcessConfiguration(
        Expression> configSelection,
        Expression>> 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(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(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(out var configuredLuaValueText))
                    set.Add(configuredLuaValueText);
            }
		
            configuredValue = set;
            successful = true;
        }
        
        if(dryRun)
            return successful;
        
        return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue);
    }
    
    /// 
    /// Attempts to process the configuration settings from a Lua table for string dictionary types.
    /// 
    /// 
    /// 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.
    /// 
    /// The ID of the related configuration plugin.
    /// The Lua table containing the settings to process.
    /// The expression to select the configuration class.
    /// The expression to select the property within the configuration class.
    /// When true, the method will not apply any changes but only check if the configuration can be read.
    /// The type of the configuration class.
    /// True when the configuration was successfully processed, otherwise false.
    public static bool TryProcessConfiguration(
        Expression> configSelection,
        Expression>> 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(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(out var key);
                var hadValue = pair.Value.TryRead(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);
    }
    /// 
    /// Handles the parsed configuration value based on whether the parsing was successful and whether it's a dry run.
    /// 
    /// The ID of the related configuration plugin.
    /// When true, no changes will be applied.
    /// Indicates whether the configuration value was successfully parsed.
    /// The configuration metadata.
    /// >The parsed configuration value.
    /// The type of the configuration class.
    /// The type of the configuration property value.
    /// True when the configuration was successfully processed, otherwise false.
    private static bool HandleParsedValue(
        Guid configPluginId,
        bool dryRun,
        bool successful,
        ConfigMeta 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 managed state:
                configMeta.SetValue(configuredValue);
                configMeta.LockManagedState(configPluginId);
                break;
            case false when configMeta.IsLocked && configMeta.MangedByConfigPluginId == 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 IsConfigurationLeftOver method, which checks if the configuration plugin
                // is still available. If it is not available, it resets the managed state of the
                // configuration setting, allowing it to be reconfigured by a different plugin or left unchanged.
                //
                configMeta.ResetManagedState();
                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;
    }
}