Editor/TypeLibraryFixes/TypeLibraryExtensions.cs
using Sandbox;
using Sandbox.Internal;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.Json;

namespace ExtendedEditor.TypeLibraryFixes;

public static class TypeLibraryExtensions
{
    public static SerializedProperty CreatePropertyFixed<T>(this TypeLibrary typeLibrary, string title,
        Func<T> get, Action<T> set, IEnumerable<Attribute>? attributes = null, SerializedObject? parent = null)
    {
        return new ActionBasedSerializedPropertyFixed(typeof(T), title, title, string.Empty,
            () => get()!, v => { if(v is T t) set(t); }, attributes, parent)
        {
            PropertyToObject = TypeSerializedPropertyFixed.PropertyToObjectFixed
        };
    }

    public static void CreateObjectValue(this SerializedProperty property)
    {
        var value = property.GetValue<object>();
        if(value != null)
            return;

        var type = property.PropertyType;

        if(property.TryGetAttribute<TypeHintAttribute>(out var typeHint))
            type = typeHint.HintedType;

        if(!type.IsValueType)
            return;

        property.SetValue(property.GetDefault());
    }

    extension(SerializedProperty)
    {
        public static T? ValueToTypeFixed<T>(T? value, T defaultValue = default!) =>
            (T)ValueToTypeFixed(typeof(T), value, defaultValue)!;

        public static object? ValueToTypeFixed(Type type, object? value, object? defaultValue = default)
        {
            try
            {
                if(value == null)
                    return defaultValue;

                if(value.GetType().IsAssignableTo(type))
                    return value;

                if(type.IsNullableValueType())
                    return ValueToTypeFixed(type.GetGenericArguments()[0], value, defaultValue);

                if(type == typeof(string))
                    return FormattableString.Invariant($"{value}");

                if(value.GetType() == typeof(string))
                    return JsonSerializer.Deserialize((string)value, type)!;

                if(type.IsEnum && value is IConvertible)
                {
                    try
                    {
                        return Enum.ToObject(type, Convert.ToInt64(value));
                    }
                    catch
                    {
                        return defaultValue;
                    }
                }

                object obj2 = Convert.ChangeType(value, type);
                if(obj2 != null)
                    return obj2;

                return JsonSerializer.SerializeToElement(value).Deserialize(type)!;
            }
            catch
            {
                return defaultValue;
            }
        }
    }

    private static readonly Assembly _reflectionAssembly = Assembly.Load("Sandbox.Reflection");
    private static readonly Assembly _systemAssembly = Assembly.Load("Sandbox.System");

    private static readonly Type _legacyTypeSerializedObjectType = _reflectionAssembly.GetType("Sandbox.Internal.TypeSerializedObject")!;
    private static readonly FieldInfo _legacyTypeSerializedObjectDesriptionField =
        _legacyTypeSerializedObjectType?.GetField("TypeDescription", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!;
    private static readonly MethodInfo _legacyTypeSerializedObjectGetTargetObjectMethod =
        _legacyTypeSerializedObjectType?.GetMethod("GetTargetObject", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, [])!;

    private static readonly FieldInfo _multiSerializedObjectChildrenField =
       typeof(MultiSerializedObject).GetField("children", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!;


    public static SerializedObject Fix(this SerializedObject obj)
    {
        if(obj is TypeSerializedObjectFixed)
            return obj;

        if(obj is SerializedCollection collection)
        {
            collection.PropertyToObject = TypeSerializedPropertyFixed.PropertyToObjectFixed;
            //TODO test
            return collection;
        }

        if(obj is MultiSerializedObject multiSerializedObject)
        {
            List<SerializedObject> children = (List<SerializedObject>)_multiSerializedObjectChildrenField.GetValue(multiSerializedObject)!;
            var childrenClone = children.ToArray();

            children.Clear();
            foreach(var child in childrenClone)
            {
                multiSerializedObject.Add(child.Fix());
            }

            multiSerializedObject.Rebuild();
            return multiSerializedObject;
        }

        if(obj.GetType() == _legacyTypeSerializedObjectType)
        {
            TypeDescription typeDescription = (TypeDescription)_legacyTypeSerializedObjectDesriptionField.GetValue(obj)!;
            object target = _legacyTypeSerializedObjectGetTargetObjectMethod.Invoke(obj, null)!;

            var type = typeDescription.TargetType;
            //TODO fix generic types

            var result = new TypeSerializedObjectFixed(target, type, obj.ParentProperty);
            result.OnPropertyChanged += obj.NoteChanged;
            return result;
        }

        throw new NotImplementedException($"Can't fix {obj.GetType()}");
    }

    private static readonly Type _legacyTypeSerializedPropertyType = _reflectionAssembly.GetType("Sandbox.Internal.TypeSerializedProperty")!;
    private static readonly FieldInfo _legacyTypeSerializedPropertyDescriptionField =
        _legacyTypeSerializedPropertyType?.GetField("prop", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!;

    private static readonly Type _legacyTypeSerializedFieldType = _reflectionAssembly.GetType("Sandbox.Internal.TypeSerializedField")!;
    private static readonly FieldInfo _legacyTypeSerializedFieldDesriptionField =
        _legacyTypeSerializedFieldType?.GetField("field", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!;

    private static readonly Type _legacyActionBasedSerializedPropertyType = _systemAssembly.GetType("Sandbox.ActionBasedSerializedProperty`1")!;


    private static bool IsFixed(this SerializedProperty property, HashSet<SerializedProperty>? tested = null)
    {
        if(tested is not null && tested.Contains(property))
            return true;

        if(property is TypeSerializedPropertyFixed)
            return true;

        if(property is ActionBasedSerializedPropertyFixed)
            return true;

        if(property is SerializedPropertyProxy proxy)
        {
            if(proxy.Fixed.HasValue)
                return proxy.Fixed.Value;

            tested ??= [];
            tested.Add(proxy);
            var result = proxy.Target.IsFixed(tested);
            proxy.Fixed = result;
            return result;
        }

        if(property.Parent is SerializedCollection)
            return true; // I hope we don't need to fix this


        var propertyType = property.GetType();

        if(propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == _legacyActionBasedSerializedPropertyType)
            return true;

        return false;
    }

    public static SerializedProperty Fix(this SerializedProperty property)
    {
        if(property.IsFixed())
            return property;

        if(property is SerializedPropertyProxy proxy)
        {
            if(proxy.Fixed == true)
                return proxy;

            return new SerializedPropertyProxy(proxy.Target.Fix(), proxy.PropertyType, proxy.GetAttributes())
            {
                Fixed = true,
            };
        }

        var propertyType = property.GetType();

        if(propertyType == _legacyTypeSerializedPropertyType)
        {
            var parentFixed = (TypeSerializedObjectFixed)property.Parent!.Fix();

            PropertyDescription propertyDescription = (PropertyDescription)_legacyTypeSerializedPropertyDescriptionField.GetValue(property)!;

            var prop = propertyDescription.DeclaringType.TargetType.GetProperty(propertyDescription.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!;
            return new TypeSerializedPropertyFixed(parentFixed, prop);
        }

        if(propertyType == _legacyTypeSerializedFieldType)
        {
            var parentFixed = (TypeSerializedObjectFixed)property.Parent!.Fix();

            FieldDescription fieldDescription = (FieldDescription)_legacyTypeSerializedFieldDesriptionField.GetValue(property)!;

            var fiel = fieldDescription.DeclaringType.TargetType.GetField(fieldDescription.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!;
            return new TypeSerializedPropertyFixed(parentFixed, fiel);
        }

        Log.Warning($"Can't fix {propertyType}");
        return property;
    }
}