Editor/ControlWidgets/GenericTypeSelectControlWidget.cs
using Editor;
using ExtendedEditor.Attributes;
using ExtendedEditor.TypeLibraryFixes;
using Sandbox;
using System;
using System.Linq;

namespace ExtendedEditor.ControlWidgets;

[CustomEditor(typeof(Type), WithAllAttributes = [typeof(TypeSelectAttribute)])]
public class GenericTypeSelectControlWidget : ControlWidget
{
    private readonly bool _whitelistedOnly;


    private Type? _lastRecordededDefinition;
    private Type[] _genericArguments = [];


    private Type? Value
    {
        get => SerializedProperty.GetValue<Type>();
        set
        {
            var currentValue = SerializedProperty.GetValue<Type>();
            if(object.Equals(currentValue, value))
                return;

            PropertyStartEdit();
            SerializedProperty.SetValue(value);
            PropertyFinishEdit();
            SignalValuesChanged();
        }
    }


    public GenericTypeSelectControlWidget(SerializedProperty property) : this(property, true)
    {
    }

    public GenericTypeSelectControlWidget(SerializedProperty property, bool whitelistedOnly) : base(property.Fix())
    {
        _whitelistedOnly = whitelistedOnly;

        Layout = Layout.Column();
        Layout.Spacing = 2;

        OnValueChanged();
    }

    private void RebuildLayout()
    {
        Layout.Clear(true);

        var baseControl = new TypeSelectControlWidget(SerializedProperty, _whitelistedOnly);
        Layout.Add(baseControl);

        var value = Value;
        if(value is not null && value.IsGenericType)
        {
            var grid = Layout.Grid();
            grid.SetMinimumColumnWidth(0, 40);
            Layout.Add(grid);

            var genericParameters = _lastRecordededDefinition!.GetGenericArguments();

            for(int i = 0; i < _genericArguments.Length; i++)
            {
                int index = i;
                var attributes = SerializedProperty.GetAttributes().ToList();
                attributes.RemoveAll(x => x is TypeSelectAttribute);
                attributes.Add(new TypeSelectAttribute()
                {
                    Validator = t => t is null || t.CanBeGenericParameter(genericParameters[index], _genericArguments)
                });

                var property = EditorTypeLibrary.CreatePropertyFixed($"<{genericParameters[index].Name}>",
                    () => GetGenericArgument(index), t => SetGenericArgument(index, t),
                    [.. attributes]);

                var widget = new GenericTypeSelectControlWidget(property, _whitelistedOnly);
                grid.AddCell(0, i, new Editor.Label(property.Name) { Alignment = TextFlag.Center });
                grid.AddCell(1, i, widget);
            }
        }
    }

    protected override void OnValueChanged()
    {
        var value = Value;
        Type? definition = value;
        if(definition is not null && definition.IsGenericType && !definition.IsGenericTypeDefinition)
            definition = definition.GetGenericTypeDefinition();

        if(!object.Equals(_lastRecordededDefinition, definition))
            _genericArguments = (value?.IsGenericType ?? false) ? value.GetGenericArguments() : [];

        _lastRecordededDefinition = definition;

        RebuildLayout();
    }

    private Type GetGenericArgument(int index)
    {
        return _genericArguments[index];
    }

    private void SetGenericArgument(int index, Type type)
    {
        _genericArguments[index] = type;

        if(_genericArguments.All(x => x is not null))
        {
            var value = Value;

            var newValue = value!.IsGenericTypeDefinition ? value : value.GetGenericTypeDefinition();
            newValue = newValue.MakeGenericType(_genericArguments);

            Value = newValue;
        }
        else
        {
            Value = _lastRecordededDefinition;
        }
    }

    protected override void OnPaint()
    {

    }
}