﻿using System.Reflection;
using SingularityGroup.HotReload.Editor;
using UnityEditor;
using UnityEngine;

[InitializeOnLoad]
public class InspectorFreezeFix
{
    // Inspector window getting stuck is fixed by calling UnityEditor.InspectorWindow.RefreshInspectors()
    // Below code subscribes to selection changed callback and calls the method if the inspector is actually stuck

    static InspectorFreezeFix()
    {
        Selection.selectionChanged += OnSelectionChanged;
    }
    
#if UNITY_6000_5_OR_NEWER
    private static EntityId _lastInitialEditorId;
#else
    private static int _lastInitialEditorId;
#endif

    private static void OnSelectionChanged() {
        if (!EditorCodePatcher.config.enableInspectorFreezeFix) {
            return;
        }
        try {
            // Most of stuff is internal so we use reflection here
            var inspectorType = typeof(Editor).Assembly.GetType("UnityEditor.InspectorWindow");

            foreach (var inspector in Resources.FindObjectsOfTypeAll(inspectorType)) {
                
                object isLockedValue = inspectorType.GetProperty("isLocked")?.GetValue(inspector);
                if (isLockedValue == null) {
                    continue;
                }
                
                // If inspector window is locked deliberately by user (via the lock icon on top-right), we don't need to refresh
                var isLocked = (bool)isLockedValue;
                if (isLocked) {
                    continue;
                }
                
                // Inspector getting stuck is due to ActiveEditorTracker of that window getting stuck internally.
                // The tracker starts returning same values from m_Tracker.activeEditors property.
                // (Root of cause of this is out of my reach as the tracker code is mainly native code)

                // We detect that by checking first element of activeEditors array
                // We do the check because we don't want to RefreshInspectors every selection change, RefreshInspectors is expensive
                var tracker = inspectorType.GetField("m_Tracker", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(inspector);
                if (tracker == null) {
                    continue;
                }
                var activeEditors = tracker.GetType().GetProperty("activeEditors");
                if (activeEditors == null) {
                    continue;
                }
                var editors = (Editor[])activeEditors.GetValue(tracker);
                if (editors.Length == 0) {
                    continue;
                }
                
#if UNITY_6000_5_OR_NEWER
                var first = editors[0].GetEntityId();
#else
                var first = editors[0].GetInstanceID();
#endif
                if (_lastInitialEditorId.Equals(first)) {
                    // This forces the tracker to be rebuilt
                    var m = inspectorType.GetMethod("RefreshInspectors", BindingFlags.Static | BindingFlags.NonPublic);
                    if (m == null) {
                        // support for older versions
                        RefreshInspectors(inspectorType);
                    } else {
                        m.Invoke(null, null);
                    }
                }
                _lastInitialEditorId = first;
                // Calling RefreshInspectors once refreshes all the editors
                break;
            }
        } catch {
            // ignore, we don't want to make user experience worse by displaying a warning in this case
        }
    }

    static void RefreshInspectors(System.Type inspectorType) {
        var allInspectorsField = inspectorType.GetField("m_AllInspectors", BindingFlags.NonPublic | BindingFlags.Static);
        
        if (allInspectorsField == null) {
            return;
        }
        var allInspectors = allInspectorsField.GetValue(null) as System.Collections.IEnumerable;
        if (allInspectors == null) {
            return;
        }
        
        foreach (var inspector in allInspectors) {
            var trackerField = FindFieldInHierarchy(inspector.GetType(), "tracker");

            if (trackerField == null) {
                continue;
            }
            var tracker = trackerField.GetValue(inspector);
            var forceRebuildMethod = tracker.GetType().GetMethod("ForceRebuild", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            if (forceRebuildMethod == null) {
                
                continue;
            }
            forceRebuildMethod.Invoke(tracker, null);
        }
    }

    static PropertyInfo FindFieldInHierarchy(System.Type type, string fieldName) {
        PropertyInfo field = null;

        while (type != null && field == null) {
            field = type.GetProperty(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            type = type.BaseType;
        }

        return field;
    }
}

