Code/Util/SerializableType.cs
namespace Nodebox.Util;

public class SerializableType : IHotloadManaged {
    public SerializableType() { }
    public SerializableType(Type type) {
        Type = type;
    }

    public static implicit operator SerializableType(Type type) {
        if (_convertCache.TryGetValue(type, out var cached)) {
            return cached;
        }

        var result = new SerializableType(type);
        _convertCache[type] = result;
        return result;
    }

    public static implicit operator Type(SerializableType type) => type.Type;

    [SkipHotload]
    private readonly static Dictionary<int, Type> _cache = [];
    [SkipHotload]
    private readonly static Dictionary<Type, SerializableType> _convertCache = [];
    [OnHotload]
    private static void InvalidateCache() {
        _cache.Clear();
        _convertCache.Clear();
    }

    [JsonIgnore]
    [Property]
    [Generic]
    [Title("Type")]
    public Type GenericTypeDefinition {
        get {
            if (!Identity.HasValue) { return null; }
            return TypeLibrary.GetTypeByIdent(Identity.Value).TargetType;
        }
        set {
            if (value == null) {
                Identity = null;
                return;
            }

            Identity = TypeLibrary.GetType(value).Identity;
        }
    }

    [JsonIgnore]
    [Generic]
    [Hide]
    public Type Type {
        get {
            var key = GetHashCode();
            if (_cache.TryGetValue(key, out var cached)) {
                return cached;
            }

            Type Get() {
                if (!Identity.HasValue) { return null; }

                var type = TypeLibrary.GetTypeByIdent(Identity.Value);
                if (!IsGeneric || GenericArguments == null || GenericArguments.Length == 0) {
                    return type.TargetType;
                }

                if (GenericArguments.Length != type.GenericArguments.Length) {
                    return null;
                }

                var inargs = CollectGenericArguments();
                if (inargs == null) {
                    return type.TargetType;
                }

                try {
                    return type.MakeGenericType(inargs);
                }
                catch (Exception e) {
                    Log.Warning(e);
                    return null;
                }
            }

            var result = Get();
            _cache[key] = result;
            return result;
        }
        set {
            if (value == null) {
                Identity = null;
                GenericArguments = null;
                return;
            }

            Identity = TypeLibrary.GetType(value).Identity;
            if (!value.IsGenericType) {
                GenericArguments = null;
                return;
            }

            if (TypeLibrary.IsGenericTypeDefinition(value)) {
                GenericArguments = new SerializableType[TypeLibrary.GetType(value).GenericArguments.Length];
                return;
            }

            var inargs = TypeLibrary.GetGenericArguments(value);
            GenericArguments = new SerializableType[inargs.Length];
            for (int i = 0; i < GenericArguments.Length; i++) {
                GenericArguments[i] = new SerializableType(inargs[i]);
            }
        }
    }

    [JsonInclude]
    [ReadOnly]
    public int? Identity { get; set; } = null;

    [JsonIgnore]
    public bool IsGeneric => Identity.HasValue && TypeLibrary.GetTypeByIdent(Identity.Value).IsGenericType;

    [JsonInclude]
    [Property]
    [ShowIf(nameof(IsGeneric), true)]
    public SerializableType[] GenericArguments { get; set; } = null;

    public T Create<T>(object[] args = null) {
        var typeDesc = TypeLibrary.GetType(Type);
        if (!typeDesc.IsGenericType) {
            return typeDesc.Create<T>(args);
        }

        var inargs = CollectGenericArguments();
        if (inargs == null) { return default; }
        return typeDesc.CreateGeneric<T>(inargs, args);
    }

    protected Type[] CollectGenericArguments() {
        if (GenericArguments == null) { return []; }
        var inargs = new Type[GenericArguments.Length];

        for (int i = 0; i < GenericArguments.Length; i++) {
            if (GenericArguments[i] == null) {
                return null;
            }

            if (GenericArguments[i].Type == null) {
                return null;
            }

            inargs[i] = GenericArguments[i].Type;
        }

        return inargs;
    }

    public override int GetHashCode() {
        var hashCode = HashCode.Combine(Identity);
        if (GenericArguments != null) {
            foreach (var argument in GenericArguments) {
                hashCode = HashCode.Combine(
                    hashCode,
                    argument?.GetHashCode()
                );
            }
        }
        return hashCode;
    }
    public override string ToString() => Type?.ToSimpleString() ?? "SerializableType(null)";
}