#if UNITY_EDITOR
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.ShortcutManagement;
using System.Reflection;
using System.Linq;
using UnityEngine.UIElements;
using UnityEngine.SceneManagement;
using UnityEditor.SceneManagement;
using System.Diagnostics;
using Type = System.Type;
using Delegate = System.Delegate;
using Action = System.Action;
using static VTabs.Libs.VUtils;
using static VTabs.Libs.VGUI;
// using static VTools.VDebug;



namespace VTabs
{
    public static class VTabs
    {

        static void UpdateGUIs()
        {
            foreach (var dockArea in allDockAreas)
                if (!guis_byDockArea.ContainsKey(dockArea))
                    guis_byDockArea[dockArea] = new VTabsGUI(dockArea);

            foreach (var dockArea in guis_byDockArea.Keys.ToList().Where(r => !r))
                guis_byDockArea.Remove(dockArea);



            foreach (var gui in guis_byDockArea.Values)
            {
                gui.UpdateScrollAnimation();
                gui.UpdateLockButtonHiding();
            }


        }

        public static Dictionary<Object, VTabsGUI> guis_byDockArea = new();







        public static void UpdateStyleSheet()
        {
            if (!Application.unityVersion.StartsWith("6000")) return;


            void updatePluginFolderPath()
            {
                if (AssetDatabase.LoadAssetAtPath<Object>(pluginFolderPath.CombinePath("VTabs.cs")) is not null) return;


                var mainScriptPath = GetScriptPath(nameof(VTabs));

                ProjectPrefs.SetString("vTabs-plugin-folder-path", mainScriptPath.GetParentPath());

            }

            void generate()
            {
                var s = "";

                void addComment()
                {
                    s +=
                    @"
/* This file is generated by vTabs to modify tab style */
/* Feel free to remove it from version control */
                    ";
                }

                void addLargeTabs()
                {
                    if (!VTabsMenu.largeTabStyleEnabled) return;

                    s +=
                    @"
/* tab text */
.dragtab
{
    padding-left: 10px;
}

/* tab itself */
.tab
{
    padding-right: 27px;
}
                    ";
                }
                void addNeatTabs()
                {
                    if (!VTabsMenu.neatTabStyleEnabled) return;

                    s +=
                    @"
/* tab text */
.dragtab
{
    padding-left: 10px;
}

/* tab itself */
.tab
{
    padding-right: -1px;
}
                    ";
                }

                void addClassicBackground_dark()
                {
                    if (!VTabsMenu.classicBackgroundEnabled) return;
                    if (!isDarkTheme) return;

                    s +=
                    @"
/* background */
.dockarea
{
     background-color: #262626;
}

/* tab text */
.dragtab
{
     color: #dedede;
}

/* top and bottom bars */
.AppToolbar
{
     background-color: #181818;
}
                    ";
                }
                void addClassicBackground_light()
                {
                    if (!VTabsMenu.classicBackgroundEnabled) return;
                    if (isDarkTheme) return;

                    s +=
                    @"
/* background */
.dockarea
{
     background-color: #a9a9a9;
}

/* top and bottom bars */
.AppToolbar
{
     background-color: #888888;
}
                    ";
                }
                void addGreyBackground()
                {
                    if (!VTabsMenu.greyBackgroundEnabled) return;

                    s +=
                    @"
/* background */
.dockarea
{
     background-color: #222222;
}

/* tab text */
.dragtab
{
     color: #dedede;
}

/* top and bottom bars */
.AppToolbar
{
     background-color: #222222;
}
                    ";
                }

                void save()
                {
                    styleSheetPath.EnsureDirExists();

                    System.IO.File.WriteAllText(styleSheetPath, s);

                    AssetDatabase.ImportAsset(styleSheetPath);

                }
                void addMetadata()
                {
                    var importer = AssetImporter.GetAtPath(styleSheetPath);

                    importer.userData = VTabsMenu.tabStyle + " " + VTabsMenu.backgroundStyle + " " + (isDarkTheme ? 1 : 0);

                    importer.Dirty();
                    importer.SaveAndReimport();

                }


                addComment();

                addLargeTabs();
                addNeatTabs();

                addClassicBackground_dark();
                addClassicBackground_light();
                addGreyBackground();

                save();
                addMetadata();

                EditorUtility.RequestScriptReload();

            }
            void delete()
            {
                System.IO.Directory.Delete(styleSheetsFolderPath, recursive: true);

                System.IO.File.Delete(styleSheetsFolderPath + ".meta");

                AssetDatabase.Refresh();

            }
            void update()
            {
                var importer = AssetImporter.GetAtPath(styleSheetPath);

                if (importer.userData == null || importer.userData.Length != 5) return;


                var tabStyle = (int)char.GetNumericValue(importer.userData[0]);
                var backgroundStyle = (int)char.GetNumericValue(importer.userData[2]);
                var wasDarkTheme = (int)char.GetNumericValue(importer.userData[4]) == 1;

                if (tabStyle != VTabsMenu.tabStyle || backgroundStyle != VTabsMenu.backgroundStyle || isDarkTheme != wasDarkTheme)
                    generate();

            }


            updatePluginFolderPath();

            var hasStyleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>(styleSheetPath) != null;
            var shouldHaveStyleSheet = (VTabsMenu.tabStyle != 0 || VTabsMenu.backgroundStyle != 0) && !VTabsMenu.pluginDisabled;


            if (shouldHaveStyleSheet && !hasStyleSheet) generate();
            if (!shouldHaveStyleSheet && hasStyleSheet) delete();

            if (shouldHaveStyleSheet && hasStyleSheet) update();

        }

        static string pluginFolderPath => ProjectPrefs.GetString("vTabs-plugin-folder-path");
        static string styleSheetsFolderPath => pluginFolderPath.CombinePath("StyleSheets");
        static string styleSheetPath => pluginFolderPath.CombinePath("StyleSheets/Extensions/common.uss");











        static void Shortcuts() // globalEventHandler
        {
            if (!curEvent.isKeyDown) return;
            if (EditorWindow.focusedWindow?.GetType() == t_GameView && t_ShortcutIntegrations.GetMemberValue<bool>("ignoreWhenPlayModeFocused") && Application.isPlaying) return;

            void addTab()
            {
                if (curEvent.keyCode != KeyCode.T) return;
                if (curEvent.modifiers != EventModifiers.Control
                 && curEvent.modifiers != EventModifiers.Command) return;

                if (!VTabsMenu.addTabShortcutEnabled) return;

                if (EditorWindow.mouseOverWindow.GetDockArea() is not Object dockArea) return;
                if (!guis_byDockArea.TryGetValue(dockArea, out var gui)) return;


                VTabsAddTabWindow.Open(dockArea);

                EditorWindow.mouseOverWindow.Repaint(); // for + button to light up 

                curEvent.Use();

            }
            void closeTab()
            {
                if (curEvent.keyCode != KeyCode.W) return;
                if (curEvent.modifiers != EventModifiers.Control
                 && curEvent.modifiers != EventModifiers.Command) return;

                if (!VTabsMenu.closeTabShortcutEnabled) return;

                if (EditorWindow.mouseOverWindow.GetDockArea() is not Object dockArea) return;
                if (!guis_byDockArea.TryGetValue(dockArea, out var gui)) return;

                if (gui.tabs.Count == 1) return;


                gui.CloseTab(gui.activeTab);

                curEvent.Use();

            }
            void reopenTab()
            {
                if (curEvent.keyCode != KeyCode.T) return;
                if (curEvent.modifiers != (EventModifiers.Command | EventModifiers.Shift)
                 && curEvent.modifiers != (EventModifiers.Control | EventModifiers.Shift)) return;

                if (!VTabsMenu.reopenTabShortcutEnabled) return;

                if (EditorWindow.mouseOverWindow.GetDockArea() is not Object dockArea) return;
                if (!guis_byDockArea.TryGetValue(dockArea, out var gui)) return;


                gui.ReopenClosedTab();

                curEvent.Use();

            }

            addTab();
            closeTab();
            reopenTab();

        }
















        static void UpdateBrowserTitle(EditorWindow browser)
        {
            if (mi_VFavorites_CanBrowserBeWrapped != null && mi_VFavorites_CanBrowserBeWrapped.Invoke(null, new[] { browser }).Equals(false)) return;

            var isLocked = browser.GetMemberValue<bool>("isLocked");
            var isTitleDefault = browser.titleContent.text == "Project";

            void setLockedTitle()
            {
                if (!isLocked) return;

                var isOneColumn = browser.GetMemberValue<int>("m_ViewMode") == 0;

                var path = isOneColumn ? browser.GetLockedFolderPath_oneColumn() : browser.InvokeMethod<string>("GetActiveFolderPath");
                var guid = path.ToGuid();

                var name = path.GetFilename();
                var icon = EditorGUIUtility.FindTexture("Project");

                if (name.StartsWith("com."))
                    if (UnityEditor.PackageManager.PackageInfo.FindForAssetPath(path) is UnityEditor.PackageManager.PackageInfo packageInfo)
                        name = packageInfo.displayName;


                void getIconFromVFolders()
                {
                    if (mi_VFolders_GetIcon == null) return;

                    if (mi_VFolders_GetIcon.Invoke(null, new[] { guid }) is Texture2D iconFromVFolders)
                        icon = iconFromVFolders;

                }

                getIconFromVFolders();


                browser.titleContent = new GUIContent(name, icon);

                t_DockArea.GetFieldValue<IDictionary>("s_GUIContents").Clear();

            }
            void setDefaultTitle()
            {
                if (isLocked) return;
                if (isTitleDefault) return;

                var name = "Project";
                var icon = EditorGUIUtility.FindTexture("Project@2x");

                browser.titleContent = new GUIContent(name, icon);

                t_DockArea.GetFieldValue<IDictionary>("s_GUIContents").Clear();

            }

            setLockedTitle();
            setDefaultTitle();

        }

        static void UpdatePropertyEditorTitle(EditorWindow propertyEditor)
        {
            var obj = propertyEditor.GetMemberValue<Object>("m_InspectedObject");

            if (!obj) return;


            var name = obj is Component component ? GetComponentName(component) : obj.name;
            var sourceIcon = AssetPreview.GetMiniThumbnail(obj);
            var adjustedIcon = sourceIcon;


            void getSourceIconFromVHierarchy()
            {
                if (mi_VHierarchy_GetIcon == null) return;
                if (obj is not GameObject gameObject) return;

                if (mi_VHierarchy_GetIcon.Invoke(null, new[] { gameObject }) is Texture2D iconFromVHierarchy)
                    sourceIcon = iconFromVHierarchy;

            }
            void getAdjustedIcon()
            {
                if (adjustedObjectIconsBySourceIid.TryGetValue(sourceIcon.GetInstanceID(), out adjustedIcon)) return;


                adjustedIcon = new Texture2D(sourceIcon.width, sourceIcon.height, sourceIcon.format, sourceIcon.mipmapCount, false);
                adjustedIcon.hideFlags = HideFlags.DontSave;
                adjustedIcon.SetPropertyValue("pixelsPerPoint", (sourceIcon.width / 16f).RoundToInt());

                Graphics.CopyTexture(sourceIcon, adjustedIcon);


                adjustedObjectIconsBySourceIid[sourceIcon.GetInstanceID()] = adjustedIcon;

            }


            getSourceIconFromVHierarchy();
            getAdjustedIcon();

            propertyEditor.titleContent = new GUIContent(name, adjustedIcon);

            propertyEditor.SetMemberValue("m_InspectedObject", null); // prevents further title updates from both internal code and vTabs

            t_DockArea.GetFieldValue<IDictionary>("s_GUIContents").Clear();

        }


        public static void UpdateTitle(EditorWindow window)
        {
            if (window == null) return;

            var isPropertyEditor = window.GetType() == t_PropertyEditor;
            var isBrowser = window.GetType() == t_ProjectBrowser;

            if (!isPropertyEditor && !isBrowser) return;


            if (isPropertyEditor)
                UpdatePropertyEditorTitle(window);

            if (isBrowser)
                if (window.GetPropertyValue<bool>("isLocked"))
                    UpdateBrowserTitle(window);

        }

        static void UpdateAllBrowserTitles()
        {
            foreach (var r in allBrowsers)
                UpdateBrowserTitle(r);
        }
        static void UpdateAllPropertyEditorTitles()
        {
            foreach (var r in allPropertyEditors)
                UpdatePropertyEditorTitle(r);
        }


        static Dictionary<int, Texture2D> adjustedObjectIconsBySourceIid = new Dictionary<int, Texture2D>();














        static void WrappedBrowserOnGUI(EditorWindow browser)
        {
            var headerHeight = 26;
            var footerHeight = 21;
            var breadcrubsYOffset = .5f;

            var headerRect = browser.position.SetPos(0, 0).SetHeight(headerHeight);
            var footerRect = browser.position.SetPos(0, 0).SetHeightFromBottom(footerHeight);
            var listAreaRect = browser.position.SetPos(0, 0).AddHeight(-footerHeight).AddHeightFromBottom(-headerHeight);

            var breadcrumbsRect = headerRect.AddHeightFromBottom(-breadcrubsYOffset * 2);
            var topGapRect = headerRect.SetHeight(breadcrubsYOffset * 2);

            var breadcrumbsTint = isDarkTheme ? Greyscale(0, .05f) : Greyscale(0, .02f);
            var topGapColor = isDarkTheme ? Greyscale(.24f, 1) : Greyscale(.8f, 1);

            var isOneColumn = browser.GetMemberValue<int>("m_ViewMode") == 0;

            var prevSelection = Selection.objects;



            void setRootForOneColumn()
            {
                if (!isOneColumn) return;
                if (curEvent.isRepaint) return;
                if (browser.GetMemberValue("m_AssetTree") is not object m_AssetTree) return;
                if (m_AssetTree.GetMemberValue("data") is not object data) return;

#if UNITY_6000_3_OR_NEWER
                var m_rootInstanceID = data.GetMemberValue<EntityId>("m_rootInstanceID");
#else
                var m_rootInstanceID = data.GetMemberValue<int>("m_rootInstanceID");
#endif


                void setInitial()
                {
                    if (m_rootInstanceID != 0) return;

                    var folderPath = browser.GetLockedFolderPath_oneColumn();
                    var folderIid = AssetDatabase.LoadAssetAtPath<Object>(folderPath).GetInstanceID();

#if UNITY_6000_3_OR_NEWER
                    data.SetMemberValue("m_rootInstanceID", (EntityId)folderIid);
#else
                    data.SetMemberValue("m_rootInstanceID", folderIid);
#endif

                    m_AssetTree.InvokeMethod("ReloadData");

                }
                void update()
                {
                    if (m_rootInstanceID == 0) return;

                    var folderIid = m_rootInstanceID;
                    var folderPath = _EditorUtility_InstanceIDToObject(folderIid).GetPath();

                    browser.SetLockedFolderPath_oneColumn(folderPath);

                    browser.GetMemberValue("m_SearchFilter")?.SetMemberValue("m_Folders", new[] { folderPath }); // needed for breadcrumbs to display correctly

                }
                void reset()
                {
                    if (browser.GetMemberValue<bool>("isLocked")) return;

#if UNITY_6000_3_OR_NEWER
                    data.SetMemberValue("m_rootInstanceID", (EntityId)0);
#else
                    data.SetMemberValue("m_rootInstanceID", 0);
#endif
                    browser.SetLockedFolderPath_oneColumn("Assets");

                    m_AssetTree.InvokeMethod("ReloadData");

                    // returns the browser to normal state on unlock

                }

                setInitial();
                update();
                reset();

            }
            void handleFolderChange()
            {
                if (isOneColumn) return;

                void onBreadcrumbsClick()
                {
                    if (!curEvent.isMouseUp) return;
                    if (!breadcrumbsRect.IsHovered()) return;

                    browser.RecordUndo();

                    toCallInGUI += () => UpdateBrowserTitle(browser);
                    toCallInGUI += () => browser.Repaint();

                }
                void onDoubleclick()
                {
                    if (!curEvent.isMouseDown) return;
                    if (curEvent.clickCount != 2) return;

                    browser.RecordUndo();

                    EditorApplication.delayCall += () => UpdateBrowserTitle(browser);
                    EditorApplication.delayCall += () => browser.Repaint();

                }
                void onUndoRedo()
                {
                    if (!curEvent.isKeyDown) return;
                    if (!curEvent.holdingCmdOrCtrl) return;
                    if (curEvent.keyCode != KeyCode.Z) return;

                    var curFolderGuid = browser.InvokeMethod<string>("GetActiveFolderPath").ToGuid();

                    EditorApplication.delayCall += () =>
                    {
                        var delayedFolderGuid = browser.InvokeMethod<string>("GetActiveFolderPath").ToGuid();

                        if (delayedFolderGuid == curFolderGuid) return;


                        var folderIid = AssetDatabase.LoadAssetAtPath<Object>(AssetDatabase.GUIDToAssetPath(delayedFolderGuid)).GetInstanceID();

#if UNITY_6000_3_OR_NEWER
                        browser.InvokeMethod("SetFolderSelection", new[] { (EntityId)folderIid }, false);
#else
                        browser.InvokeMethod("SetFolderSelection", new[] { folderIid }, false);
#endif

                        UpdateBrowserTitle(browser);

                    };

                }

                onBreadcrumbsClick();
                onDoubleclick();
                onUndoRedo();

            }

            void oneColumn()
            {
                if (!isOneColumn) return;




                if (!browser.InvokeMethod<bool>("Initialized"))
                    browser.InvokeMethod("Init");



                var m_TreeViewKeyboardControlID = GUIUtility.GetControlID(FocusType.Keyboard);







                browser.InvokeMethod("OnEvent");

                if (curEvent.isMouseDown && browser.position.SetPos(0, 0).IsHovered())
                    t_ProjectBrowser.SetFieldValue("s_LastInteractedProjectBrowser", browser);




                // header
                browser.SetFieldValue("m_ListHeaderRect", breadcrumbsRect);

                if (curEvent.isRepaint)
                    browser.InvokeMethod("BreadCrumbBar");

                breadcrumbsRect.Draw(breadcrumbsTint);
                topGapRect.Draw(topGapColor);

                breadcrumbsRect.SetHeightFromBottom(1).Draw(Greyscale(.14f));




                // footer
                browser.SetFieldValue("m_BottomBarRect", footerRect);
                browser.InvokeMethod("BottomBar");




                // tree
                browser.GetMemberValue("m_AssetTree")?.InvokeMethod("OnGUI", listAreaRect, m_TreeViewKeyboardControlID);












                browser.InvokeMethod("HandleCommandEvents");



            }
            void twoColumns()
            {
                if (isOneColumn) return;



                if (!browser.InvokeMethod<bool>("Initialized"))
                    browser.InvokeMethod("Init");



                var m_ListKeyboardControlID = GUIUtility.GetControlID(FocusType.Keyboard);

                var startGridSize = browser.GetFieldValue("m_ListArea")?.GetMemberValue("gridSize");





                browser.InvokeMethod("OnEvent");

                if (curEvent.isMouseDown && browser.position.SetPos(0, 0).IsHovered())
                    t_ProjectBrowser.SetFieldValue("s_LastInteractedProjectBrowser", browser);




                // header
                browser.SetFieldValue("m_ListHeaderRect", breadcrumbsRect);


                browser.InvokeMethod("BreadCrumbBar");

                breadcrumbsRect.Draw(breadcrumbsTint);
                topGapRect.Draw(topGapColor);

                breadcrumbsRect.SetHeightFromBottom(1).Draw(Greyscale(.14f));




                // footer
                browser.SetFieldValue("m_BottomBarRect", footerRect);
                browser.InvokeMethod("BottomBar");




                // list area
                browser.GetFieldValue("m_ListArea").InvokeMethod("OnGUI", listAreaRect, m_ListKeyboardControlID);

                // block grid size changes when ctrl-shift-scrolling
                if (curEvent.holdingCmdOrCtrl)
                    browser.GetFieldValue("m_ListArea").SetMemberValue("gridSize", startGridSize);





                browser.SetFieldValue("m_StartGridSize", browser.GetFieldValue("m_ListArea").GetMemberValue("gridSize"));

                browser.InvokeMethod("HandleContextClickInListArea", listAreaRect);
                browser.InvokeMethod("HandleCommandEvents");



            }

            void preventSelectionChangeInOtherBrowsers()
            {
                if (Selection.objects.SequenceEqual(prevSelection)) return;


                allBrowsers.ForEach(r => r?.SetMemberValue("m_InternalSelectionChange", true));

                EditorApplication.delayCall += () =>
                    allBrowsers.ForEach(r => r?.SetMemberValue("m_InternalSelectionChange", false));

            }



            setRootForOneColumn();
            handleFolderChange();

            oneColumn();
            twoColumns();

            preventSelectionChangeInOtherBrowsers();

        }


        static void UpdateWrappingForBrowser(EditorWindow browser)
        {
            if (!browser.hasFocus) return;
            if (mi_VFavorites_CanBrowserBeWrapped != null && mi_VFavorites_CanBrowserBeWrapped.Invoke(null, new[] { browser }).Equals(false)) return;

            var isWrapped = browser.GetMemberValue("m_Parent").GetMemberValue<Delegate>("m_OnGUI").Method == mi_WrappedBrowserOnGUI;

            var isLocked = browser.GetMemberValue<bool>("isLocked");
            var shouldBeWrapped = isLocked && !VTabsGUI.disableWrapping;

            void wrap()
            {
                if (isWrapped) return;
                if (!shouldBeWrapped) return;

                var hostView = browser.GetMemberValue("m_Parent");

                var newDelegate = typeof(VTabs).GetMethod(nameof(WrappedBrowserOnGUI), maxBindingFlags).CreateDelegate(t_EditorWindowDelegate, browser);

                hostView.SetMemberValue("m_OnGUI", newDelegate);

                browser.Repaint();


                browser.SetMemberValue("useTreeViewSelectionInsteadOfMainSelection", false);

            }
            void unwrap()
            {
                if (!isWrapped) return;
                if (shouldBeWrapped) return;

                var hostView = browser.GetMemberValue("m_Parent");

                var originalDelegate = hostView.InvokeMethod("CreateDelegate", "OnGUI");

                hostView.SetMemberValue("m_OnGUI", originalDelegate);

                browser.Repaint();

            }

            wrap();
            unwrap();

        }

        static void UpdateWrappingForAllBrowsers()
        {
            foreach (var r in allBrowsers)
                UpdateWrappingForBrowser(r);
        }














        static void HideTabScrollerButtons()
        {
            void getStyles()
            {
                if (leftScrollerStyle != null && rightScrollerStyle != null) return;

                if (!guiStylesInitialized) TryInitializeGuiStyles();
                if (!guiStylesInitialized) return;

                if (typeof(GUISkin).GetFieldValue("current")?.GetFieldValue<Dictionary<string, GUIStyle>>("m_Styles")?.ContainsKey("dragtab scroller prev") != true) return;
                if (typeof(GUISkin).GetFieldValue("current")?.GetFieldValue<Dictionary<string, GUIStyle>>("m_Styles")?.ContainsKey("dragtab scroller next") != true) return;


                var t_Styles = typeof(Editor).Assembly.GetType("UnityEditor.DockArea+Styles");

                leftScrollerStyle = t_Styles.GetFieldValue<GUIStyle>("tabScrollerPrevButton");
                rightScrollerStyle = t_Styles.GetFieldValue<GUIStyle>("tabScrollerNextButton");

            }
            void createTexture()
            {
                if (clearTexture != null) return;

                clearTexture = new Texture2D(1, 1);
                clearTexture.hideFlags = HideFlags.DontSave;
                clearTexture.SetPixel(0, 0, Color.clear);
                clearTexture.Apply();

            }
            void assignTexture()
            {
                if (leftScrollerStyle == null) return;
                if (rightScrollerStyle == null) return;

                leftScrollerStyle.normal.background = clearTexture;
                rightScrollerStyle.normal.background = clearTexture;

            }

            getStyles();
            createTexture();
            assignTexture();

        }

        static GUIStyle leftScrollerStyle;
        static GUIStyle rightScrollerStyle;

        static Texture2D clearTexture;














        static void ReplaceUnloadedPropertyEditors_withPlaceholderWIndows()
        {
            // closes non-prefabs too because m_InspectedObject is set to null when changing title
            // and doesn't work that reliably anyway
            // so I just commented it 


            // foreach (var propertyEditor in allPropertyEditors)
            //     if (propertyEditor.GetMemberValue<Object>("m_InspectedObject") == null)

            //         ScriptableObject.CreateInstance<VTabsPlaceholderWindow>()
            //                         .Open_andReplacePropertyEditor(propertyEditor);

        }

        static void LoadPropertyEditorInspectedObjects()
        {
            foreach (var propertyEditor in allPropertyEditors)
                propertyEditor.InvokeMethod("LoadPersistedObject");

        }

        static void EnsureActiveTabsVisibleOnScroller()
        {
            foreach (var dockArea in allDockAreas)
            {
                if (!guis_byDockArea.TryGetValue(dockArea, out var gui)) continue;


                var scrollPos = gui.GetTargetScrollPosition();

                if (!scrollPos.Approx(0))
                    scrollPos += gui.nonZeroTabScrollOffset;


                dockArea.SetFieldValue("m_ScrollOffset", scrollPos);

            }
        }

        public static void RepaintAllDockAreas()
        {
            foreach (var dockarea in allDockAreas)
                dockarea.InvokeMethod("Repaint");
        }












        [UnityEditor.Callbacks.PostProcessBuild]
        static void OnBuild(BuildTarget _, string __)
        {
            EditorApplication.delayCall += LoadPropertyEditorInspectedObjects;
            EditorApplication.delayCall += UpdateAllPropertyEditorTitles;
        }

        static void OnDomainReloaded()
        {
            toCallInGUI += UpdateWrappingForAllBrowsers;
            toCallInGUI += UpdateAllBrowserTitles;

        }

        static void OnSceneOpened(Scene _, OpenSceneMode __)
        {
            LoadPropertyEditorInspectedObjects();
            ReplaceUnloadedPropertyEditors_withPlaceholderWIndows();
            UpdateAllPropertyEditorTitles();

        }

        static void OnPrefabStageClosing(PrefabStage _)
        {
            ReplaceUnloadedPropertyEditors_withPlaceholderWIndows();
        }

        static void OnProjectLoaded()
        {
            toCallInGUI += EnsureActiveTabsVisibleOnScroller;

            UpdateAllPropertyEditorTitles();

        }

        static void OnFocusedWindowChanged()
        {
            if (EditorWindow.focusedWindow?.GetType() == t_ProjectBrowser)
                UpdateWrappingForBrowser(EditorWindow.focusedWindow);

        }

        static void OnWindowUnmaximized()
        {
            UpdateAllPropertyEditorTitles();
            UpdateAllBrowserTitles();

            UpdateWrappingForAllBrowsers();


            EnsureActiveTabsVisibleOnScroller();

        }






        static void CheckIfFocusedWindowChanged()
        {
            if (prevFocusedWindow != EditorWindow.focusedWindow)
                OnFocusedWindowChanged();

            prevFocusedWindow = EditorWindow.focusedWindow;
        }

        static EditorWindow prevFocusedWindow;



        static void CheckIfWindowWasUnmaximized()
        {
            var isMaximized = EditorWindow.focusedWindow?.maximized == true;

            if (!isMaximized && wasMaximized)
                OnWindowUnmaximized();

            wasMaximized = isMaximized;

        }

        static bool wasMaximized;



        static void OnSomeGUI()
        {
            toCallInGUI?.Invoke();
            toCallInGUI = null;

            CheckIfFocusedWindowChanged();

        }

        static void ProjectWindowItemOnGUI(string _, Rect __) => OnSomeGUI();
        static void HierarchyWindowItemOnGUI(int _, Rect __) => OnSomeGUI();

        static Action toCallInGUI;



        static void DelayCallLoop()
        {
            UpdateAllBrowserTitles();
            UpdateWrappingForAllBrowsers();
            HideTabScrollerButtons();

            EditorApplication.delayCall -= DelayCallLoop;
            EditorApplication.delayCall += DelayCallLoop;

        }



        static void Update()
        {
            CheckIfFocusedWindowChanged();
            CheckIfWindowWasUnmaximized();
        }














        static void ComponentTabHeaderGUI(Editor editor)
        {
            if (editor.target is not Component component) return;

            var headerRect = ExpandWidthLabelRect(height: 0).MoveY(-48).SetHeight(50).AddWidthFromMid(8);
            var nameRect = headerRect.MoveX(43).MoveY(5).SetHeight(20).SetXMax(headerRect.xMax - 50);
            var subtextRect = headerRect.MoveX(43).MoveY(22).SetHeight(20);


            void hideName()
            {
                var maskRect = headerRect.AddWidthFromRight(-45).AddWidth(-50);

                var maskColor = Greyscale(isDarkTheme ? .24f : .8f);

                maskRect.Draw(maskColor);

            }
            void name()
            {
                SetLabelFontSize(13);

                GUI.Label(nameRect, GetComponentName(component));

                ResetLabelStyle();

            }
            void componentOf()
            {
                SetGUIEnabled(false);

                GUI.Label(subtextRect, "Component of");

                ResetGUIEnabled();

            }
            void goName()
            {
                var goNameRect = subtextRect.MoveX("Component of ".GetLabelWidth() - 3).SetWidth(component.gameObject.name.GetLabelWidth(isBold: true));

                goNameRect.MarkInteractive();

                SetGUIEnabled(goNameRect.IsHovered() && !mousePressedOnGoName);
                SetLabelBold();

                GUI.Label(goNameRect, component.gameObject.name);

                ResetGUIEnabled();
                ResetLabelStyle();



                if (curEvent.isMouseDown && goNameRect.IsHovered())
                {
                    mousePressedOnGoName = true;
                    curEvent.Use();
                }

                if (curEvent.isMouseUp)
                {
                    if (mousePressedOnGoName)
                        EditorGUIUtility.PingObject(component.gameObject);

                    mousePressedOnGoName = false;
                    curEvent.Use();
                }

                if (curEvent.isMouseLeaveWindow || (!curEvent.isLayout && !goNameRect.Resize(1).IsHovered()))
                    mousePressedOnGoName = false;

            }



            hideName();
            name();
            componentOf();
            goName();

            Space(-4);

        }

        static bool mousePressedOnGoName;

        static string GetComponentName(Component component)
        {
            if (!component) return "";

            var name = new GUIContent(EditorGUIUtility.ObjectContent(component, component.GetType())).text;

            name = name.Substring(name.LastIndexOf('(') + 1);
            name = name.Substring(0, name.Length - 1);

            return name;

        }














        static void SetupExtraScrollerSpace()
        {

            var action = t_WindowAction.GetMethod("CreateWindowMenuItem", maxBindingFlags).Invoke(null, new[] { "vTabs dummy for creating extra space for + button", null, null });


            var extraSpaceAmount = 21f;

            action.SetMemberValue("width", extraSpaceAmount);


            var mi_ShouldCreateExtraSpace = typeof(VTabs).GetMethod("ShouldCreateExtraSpace", maxBindingFlags);

            var funcDelegate = Delegate.CreateDelegate(t_ValidateHandler, mi_ShouldCreateExtraSpace);

            action.SetMemberValue("validateHandler", funcDelegate);




            var defaultActions = t_HostView.GetMemberValue<System.Array>("s_windowActions") ?? t_HostView.InvokeMethod<System.Array>("FetchWindowActionFromAttribute");

            var newActions = System.Array.CreateInstance(t_WindowAction, defaultActions.Length + 1);

            System.Array.Copy(defaultActions, newActions, defaultActions.Length);

            newActions.SetValue(action, defaultActions.Length);


            t_HostView.SetMemberValue("s_windowActions", newActions);

        }

        static bool ShouldCreateExtraSpace(EditorWindow window, object _) => VTabsMenu.addTabButtonEnabled && window == window.GetDockArea().GetTabs()?.Last();













        static void TryInitializeGuiStyles() => EditorWindow.focusedWindow?.SendEvent(EditorGUIUtility.CommandEvent(""));

        static bool guiStylesInitialized => typeof(GUI).GetFieldValue("s_Skin") != null;




        static Object GetDockArea(this EditorWindow window)
        {
            var parent = window?.GetFieldValue<Object>("m_Parent");

            if (parent?.GetType() == t_DockArea)
                return parent;
            else
                return null;

        }

        public static List<EditorWindow> GetTabs(this Object dockArea) => dockArea?.GetFieldValue<List<EditorWindow>>("m_Panes");




        public static string GetLockedFolderPath_oneColumn(this EditorWindow browser)
        {
            var path = browser.GetMemberValue<string[]>("m_LastFolders")?.FirstOrDefault();

            if (path == null || path == "Assets")
                path = browser.GetMemberValue("m_SearchFilter")?.GetMemberValue<string[]>("m_Folders")?.FirstOrDefault() ?? "Assets"; // to migrate locked folder paths from 2.0.14

            return path;

            // unlike in two column layout, there's no such concept as active folder path in one column
            // so we have to serialize locked folder path in some unused field
            // m_LastFolders appears to work fine for this purpose
            // m_SearchFilter was used before v2.0.15 but could get changed when moving/creating/deleting assets

        }

        public static void SetLockedFolderPath_oneColumn(this EditorWindow browser, string folderPath)
        {
            browser.SetMemberValue("m_LastFolders", new[] { folderPath });
        }














        [System.Serializable]
        public class TabInfo
        {
            public TabInfo(EditorWindow window)
            {
                typeName = window.GetType().Name;
                originalDockArea = window.GetDockArea();
                originalTabIndex = window.GetDockArea().GetTabs().IndexOf(window);
                wasFocused = window.hasFocus;
                originalTitle = window.titleContent.text;
                menuItemName = window.titleContent.text.Replace("/", " \u2215 ").Trim(' ');

                if (isBrowser)
                {
                    isLocked = window.GetPropertyValue<bool>("isLocked");


                    savedGridSize = window.GetFieldValue<int>("m_StartGridSize");

                    isGridSizeSaved = true;


                    savedLayout = window.GetMemberValue<int>("m_ViewMode");

                    isLayoutSaved = true;


                    var folderPath = savedLayout == 0 ? window.GetLockedFolderPath_oneColumn()                   // one column
                                                      : window.InvokeMethod<string>("GetActiveFolderPath");     // two columns
                    folderGuid = folderPath.ToGuid();

                }

                if (isPropertyEditor)
                    globalId = new GlobalID(window.GetMemberValue<string>("m_GlobalObjectId"));

            }
            public TabInfo(Object lockTo)
            {
                isLocked = true;
                typeName = lockTo is DefaultAsset ? t_ProjectBrowser.Name : t_PropertyEditor.Name;


                if (isBrowser)
                    folderGuid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(lockTo));

                if (isPropertyEditor)
                    globalId = lockTo.GetGlobalID();

#if UNITY_2021_2_OR_NEWER
                if (isPropertyEditor)
                    if (StageUtility.GetCurrentStage() is PrefabStage && globalId.ToString().Contains("00000000000000000000000000000000"))
                        lockedPrefabAssetObject = lockTo;
#endif

            }
            public TabInfo(string typeName, string menuItemName) { this.typeName = typeName; this.menuItemName = menuItemName; }

            public string typeName;
            public string menuItemName;
            public object originalDockArea;
            public int originalTabIndex;
            public string originalTitle;
            public bool wasFocused;

            public bool isBrowser => typeName == t_ProjectBrowser.Name;
            public bool isLocked;
            public string folderGuid = "";
            public int savedGridSize;
            public int savedLayout;
            public bool isGridSizeSaved = false;
            public bool isLayoutSaved = false;

            public bool isPropertyEditor => typeName == t_PropertyEditor.Name;
            public GlobalID globalId;
            public Object lockedPrefabAssetObject;

        }

        [System.Serializable]
        class TabInfoList { public List<TabInfo> list = new List<TabInfo>(); }











        [InitializeOnLoadMethod]
        static void Init()
        {
            if (VTabsMenu.pluginDisabled) return;


            EditorApplication.update -= UpdateGUIs;
            EditorApplication.update += UpdateGUIs;



            // shortcuts
            var globalEventHandler = typeof(EditorApplication).GetFieldValue<EditorApplication.CallbackFunction>("globalEventHandler");
            typeof(EditorApplication).SetFieldValue("globalEventHandler", (globalEventHandler - Shortcuts) + Shortcuts);


            // component tabs
            Editor.finishedDefaultHeaderGUI -= ComponentTabHeaderGUI;
            Editor.finishedDefaultHeaderGUI += ComponentTabHeaderGUI;




            // state change detectors
            var projectWasLoaded = typeof(EditorApplication).GetFieldValue<UnityEngine.Events.UnityAction>("projectWasLoaded");
            typeof(EditorApplication).SetFieldValue("projectWasLoaded", (projectWasLoaded - OnProjectLoaded) + OnProjectLoaded);

            UnityEditor.SceneManagement.EditorSceneManager.sceneOpened -= OnSceneOpened;
            UnityEditor.SceneManagement.EditorSceneManager.sceneOpened += OnSceneOpened;

            EditorApplication.projectWindowItemOnGUI -= ProjectWindowItemOnGUI;
            EditorApplication.projectWindowItemOnGUI += ProjectWindowItemOnGUI;

            EditorApplication.hierarchyWindowItemOnGUI -= HierarchyWindowItemOnGUI;
            EditorApplication.hierarchyWindowItemOnGUI += HierarchyWindowItemOnGUI;

            EditorApplication.delayCall -= DelayCallLoop;
            EditorApplication.delayCall += DelayCallLoop;

            EditorApplication.update -= Update;
            EditorApplication.update += Update;

            EditorApplication.quitting -= VTabsCache.Save;
            EditorApplication.quitting += VTabsCache.Save;


            PrefabStage.prefabStageClosing += OnPrefabStageClosing;



            // EditorApplication.delayCall += () => VTabsAddTabWindow.UpdateAllEntries();

            EditorApplication.delayCall += () => UpdateStyleSheet();

            SetupExtraScrollerSpace();

            OnDomainReloaded();

        }


        public static IEnumerable<EditorWindow> allBrowsers => _allBrowsers ??= t_ProjectBrowser.GetFieldValue<IList>("s_ProjectBrowsers").Cast<EditorWindow>();
        public static IEnumerable<EditorWindow> _allBrowsers;

        public static IEnumerable<EditorWindow> allPropertyEditors => Resources.FindObjectsOfTypeAll(t_PropertyEditor).Where(r => r.GetType().BaseType == typeof(EditorWindow)).Cast<EditorWindow>();

        public static List<EditorWindow> allEditorWindows
        {
            get
            {
                if (typeof(EditorWindow).GetPropertyInfo("activeEditorWindows") == null) // this variable doesn't exist in early 2022.3 versions, even though UnityCsReference repo says it does
                    return Resources.FindObjectsOfTypeAll(typeof(EditorWindow)).Cast<EditorWindow>().ToList();

                return _allEditorWindows ?? typeof(EditorWindow).GetMemberValue<List<EditorWindow>>("activeEditorWindows");
            }
        }
        public static List<EditorWindow> _allEditorWindows;

        public static IEnumerable<Object> allDockAreas => allEditorWindows.Where(r => r.hasFocus && !r.maximized).Select(r => r.GetMemberValue<Object>("m_Parent")).Where(r => r.GetType() == t_DockArea);



        public static Type t_DockArea = typeof(Editor).Assembly.GetType("UnityEditor.DockArea");
        public static Type t_PropertyEditor = typeof(Editor).Assembly.GetType("UnityEditor.PropertyEditor");
        public static Type t_ProjectBrowser = typeof(Editor).Assembly.GetType("UnityEditor.ProjectBrowser");
        public static Type t_SceneHierarchyWindow = typeof(Editor).Assembly.GetType("UnityEditor.SceneHierarchyWindow");
        public static Type t_InspectorWindow = typeof(Editor).Assembly.GetType("UnityEditor.InspectorWindow");
        public static Type t_WindowAction = typeof(Editor).Assembly.GetType("UnityEditor.WindowAction");
        public static Type t_HostView = typeof(Editor).Assembly.GetType("UnityEditor.HostView");
        public static Type t_EditorWindowDelegate = t_HostView.GetNestedType("EditorWindowDelegate", maxBindingFlags);
        public static Type t_ValidateHandler = t_WindowAction.GetNestedType("ValidateHandler", maxBindingFlags);
        public static Type t_EditorWindowShowButtonDelegate = t_HostView.GetNestedType("EditorWindowShowButtonDelegate", maxBindingFlags);
        public static Type t_GameView = typeof(Editor).Assembly.GetType("UnityEditor.GameView");
        public static Type t_ShortcutIntegrations = typeof(Editor).Assembly.GetType("UnityEditor.ShortcutManagement.ShortcutIntegration");

        public static Type t_VHierarchy = Type.GetType("VHierarchy.VHierarchy") ?? Type.GetType("VHierarchy.VHierarchy, VHierarchy, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null");
        public static Type t_VFolders = Type.GetType("VFolders.VFolders") ?? Type.GetType("VFolders.VFolders, VFolders, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null");
        public static Type t_VFavorites = Type.GetType("VFavorites.VFavorites") ?? Type.GetType("VFavorites.VFavorites, VFavorites, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null");

        public static MethodInfo mi_WrappedBrowserOnGUI = typeof(VTabs).GetMethod(nameof(WrappedBrowserOnGUI), maxBindingFlags);

        public static MethodInfo mi_VFolders_GetIcon = t_VFolders?.GetMethod("GetSmallFolderIcon_forVTabs", maxBindingFlags);
        public static MethodInfo mi_VHierarchy_GetIcon = t_VHierarchy?.GetMethod("GetIcon_forVTabs", maxBindingFlags);
        public static MethodInfo mi_VFavorites_BeforeWindowCreated = t_VFavorites?.GetMethod("BeforeWindowCreated_byVTabs", maxBindingFlags);
        public static MethodInfo mi_VFavorites_CanBrowserBeWrapped = t_VFavorites?.GetMethod("CanBrowserBeWrapped_byVTabs", maxBindingFlags);





        const string version = "2.1.6";

    }
}
#endif
