using System.Collections;
using System.Reflection;

namespace ScadaLink.Commons.Types;

/// <summary>
/// Normalizes the loosely-typed <c>parameters</c> argument of a script call
/// (<c>Scripts.CallShared</c>, <c>Instance.CallScript</c>,
/// <c>Children["X"].CallScript</c>, <c>Parent.CallScript</c>,
/// <c>Route.To().Call</c>) into the dictionary the runtime carries.
///
/// Accepts: <c>null</c>; an existing dictionary; or any object whose public
/// properties become the parameter entries — so callers can pass an anonymous
/// object, <c>new { name = "Bob", count = 3 }</c>, instead of building a
/// <c>Dictionary&lt;string, object?&gt;</c> by hand.
/// </summary>
public static class ScriptArgs
{
    public static IReadOnlyDictionary<string, object?>? Normalize(object? parameters)
    {
        switch (parameters)
        {
            case null:
                return null;
            case IReadOnlyDictionary<string, object?> roDict:
                return roDict;
            case IDictionary<string, object?> dict:
                return new Dictionary<string, object?>(dict);
            case IDictionary raw:
            {
                var result = new Dictionary<string, object?>();
                foreach (DictionaryEntry entry in raw)
                    result[entry.Key?.ToString() ?? string.Empty] = entry.Value;
                return result;
            }
        }

        var type = parameters.GetType();
        if (type.IsPrimitive || parameters is string or decimal)
            throw new ArgumentException(
                $"Script call parameters must be an object or dictionary, not {type.Name}.",
                nameof(parameters));

        var bag = new Dictionary<string, object?>();
        foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            if (prop.GetIndexParameters().Length > 0) continue;
            bag[prop.Name] = prop.GetValue(parameters);
        }
        return bag;
    }
}
