using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using UnityEditor.SceneManagement;
using UnityEngine.SceneManagement;

namespace Jovian.Recents {
    public class RecentAssets : EditorWindow {

        [System.Serializable]
        private class AssetSelection {
            public bool expanded;
            public string assetPath;

            [System.NonSerialized]
            private Object actualAsset;

            public Object Asset {
                get {
                    if(actualAsset == null) {
                        actualAsset = AssetDatabase.LoadMainAssetAtPath(assetPath);
                    }
                    return actualAsset;
                }
                set => actualAsset = value;
            }

            public List<SubAssetSelection> subAssets;
            public bool isPinned;
            public string FileName => Path.GetFileName(assetPath);
        }

        [System.Serializable]
        public struct SubAssetSelection {
            // In the current structure of this tool, to reload this object is quite messy.
            // The SubAsset doesn't know if the current context, e.g. scene, is the one it was previously selected from.
            // There might be another asset in this other scene also called "Main Camera" etc
            [System.NonSerialized]
            public Object subAsset;

            public string subAssetPath;
        }

        private bool subscribeToSelectionEvents;

        private bool SubscribeToSelectionEvents {
            set {
                if(value) {
                    Selection.selectionChanged -= OnSelectionChanged;
                    Selection.selectionChanged += OnSelectionChanged;
                }
                else {
                    Selection.selectionChanged -= OnSelectionChanged;
                }
                subscribeToSelectionEvents = value;
            }
        }

        private bool subscribeToPrefabOpenEvents;

        private bool SubscribeToPrefabOpen {
            set {
                PrefabStage.prefabStageOpened -= OnPrefabStageOpened;
                PrefabStage.prefabStageOpened += OnPrefabStageOpened;
                PrefabStage.prefabStageClosing -= OnPrefabStageClosing;
                PrefabStage.prefabStageClosing += OnPrefabStageClosing;
                subscribeToPrefabOpenEvents = value;
            }
        }

        private bool subscribeToSceneOpenEvents;

        private bool SubscribeToSceneOpenEvents {
            set {
                if(value) {
                    EditorSceneManager.activeSceneChangedInEditMode -= OnActiveSceneChangedInEditMode;
                    EditorSceneManager.activeSceneChangedInEditMode += OnActiveSceneChangedInEditMode;
                }
                else {
                    EditorSceneManager.activeSceneChangedInEditMode -= OnActiveSceneChangedInEditMode;
                }
                subscribeToSceneOpenEvents = value;
            }
        }

        private string EditorPrefsSettingsSelectionKey => $"{Application.dataPath}Jovian_RecentOpenedAssetsWindow_AssetSelections_Selections";
        private string EditorPrefsSettingsPrefabsKey => $"{Application.dataPath}Jovian_RecentOpenedAssetsWindow_AssetSelections_Prefabs";
        private string EditorPrefsSettingsScenesKey => $"{Application.dataPath}Jovian_RecentOpenedAssetsWindow_AssetSelections_Scenes";
        private string EditorPrefsSettingsCountKey => $"{Application.dataPath}Jovian_RecentOpenedAssetsWindow_AssetSelections_MaxHistory";
        private string EditorPrefsSettingsHistoryKey => $"{Application.dataPath}Jovian_RecentOpenedAssetsWindow_AssetSelections";

        private const string PinnedIcon = "IN LockButton on";
        private const string UnPinnedIcon = "IN LockButton";

        [System.Serializable]
        private class JsonWrapper {
            public JsonWrapper(List<AssetSelection> data) {
                this.data = data;
            }

            public List<AssetSelection> data;
        }

        private List<AssetSelection> selectionHistory = new();
        private int maxHistory = 10;
        private Vector2 scrollPos;

        private PrefabStage prefabStage;
        private GUIStyle guiStyle;

        [MenuItem("Jovian/Assets History...", false, 20)]
        private static void Init() {
            var window = GetWindow<RecentAssets>(false, "Assets History");
            window.minSize = new Vector2(100f, 100f);
            window.Focus();
        }

        private void OnEnable() {
            // In case of Editor reload, perhaps because of recompile, events needs to be resubscribed
            LoadSettings();
            SubscribeToSelectionEvents = subscribeToSelectionEvents;
            SubscribeToSceneOpenEvents = subscribeToSceneOpenEvents;
            SubscribeToPrefabOpen = subscribeToPrefabOpenEvents;

            LoadEntries();
        }

        private void OnDisable() {
            SaveEntries();
            SaveSettings();
        }

        private void OnDestroy() {
            SubscribeToSelectionEvents = false;
            SubscribeToSceneOpenEvents = false;
            SubscribeToPrefabOpen = false;
        }
        private void SaveEntries() {
            var jsonList = JsonUtility.ToJson(new JsonWrapper(selectionHistory));
            EditorPrefs.SetString(EditorPrefsSettingsHistoryKey, jsonList);
        }

        private void SaveSettings() {
            EditorPrefs.SetBool(EditorPrefsSettingsSelectionKey, subscribeToSelectionEvents);
            EditorPrefs.SetBool(EditorPrefsSettingsPrefabsKey, subscribeToPrefabOpenEvents);
            EditorPrefs.SetBool(EditorPrefsSettingsScenesKey, subscribeToSceneOpenEvents);
            EditorPrefs.SetInt(EditorPrefsSettingsCountKey, maxHistory);
        }

        private void LoadEntries() {
            var jsonList = EditorPrefs.GetString(EditorPrefsSettingsHistoryKey);
            var oldList = JsonUtility.FromJson<JsonWrapper>(jsonList)?.data;
            if(oldList != null) {
                selectionHistory = oldList;
            }
        }

        private void LoadSettings() {
            if(EditorPrefs.HasKey(EditorPrefsSettingsSelectionKey)) {
                subscribeToSelectionEvents = EditorPrefs.GetBool(EditorPrefsSettingsSelectionKey);
            }
            if(EditorPrefs.HasKey(EditorPrefsSettingsPrefabsKey)) {
                subscribeToPrefabOpenEvents = EditorPrefs.GetBool(EditorPrefsSettingsPrefabsKey);
            }
            if(EditorPrefs.HasKey(EditorPrefsSettingsScenesKey)) {
                subscribeToSceneOpenEvents = EditorPrefs.GetBool(EditorPrefsSettingsScenesKey);
            }
            if(EditorPrefs.HasKey(EditorPrefsSettingsCountKey)) {
                maxHistory = EditorPrefs.GetInt(EditorPrefsSettingsCountKey);
            }
        }

        private void OnActiveSceneChangedInEditMode(Scene previousScene, Scene newScene) {
            var selectedObject = AssetDatabase.LoadMainAssetAtPath(newScene.path);
            if(selectedObject != null) {
                OnAssetInteracted(selectedObject, newScene.path);
            }
        }


        private void OnPrefabStageOpened(PrefabStage prefab) {
            // We actually need to know if we're in prefabStage because of how asset parenting works during prefab editing, e.g. when _subscribeToSelectionEvents==true;
            prefabStage = prefab;
            if(subscribeToPrefabOpenEvents) {
                OnAssetInteracted(prefab.prefabContentsRoot, prefab.assetPath);
            }
        }

        private void OnPrefabStageClosing(PrefabStage prefab) {
            prefabStage = null;
        }

        private void OnSelectionChanged() {
            var selectedObject = Selection.activeObject;
            if(!selectedObject) {
                return; // When you select something in e.g. Project View that isn't an asset or object, perhaps the category header "Packages"
            }
            string assetPath;
            if(prefabStage != null && selectedObject is GameObject selectedGameObject && prefabStage.IsPartOfPrefabContents(selectedGameObject)) {
                assetPath = prefabStage.assetPath; // If we're in a PrefabStage and selecting a child GO, the assetPath is the actual prefab itself, not this potentially nested entity
            }
            else {
                assetPath = AssetDatabase.GetAssetOrScenePath(selectedObject);
            }

            OnAssetInteracted(selectedObject, assetPath);
        }

        private void OnAssetInteracted(Object selectedObject, string assetPath) {
            AssetSelection selection = null;
            for(var i = 0; i < selectionHistory.Count; i++) {
                if(selectionHistory[i].assetPath == assetPath) {
                    selection = selectionHistory[i];
                    selectionHistory.RemoveAt(i);
                    break;
                }
            }

            if(selection == null) {
                // This is a new asset selection
                selection = new AssetSelection {
                    expanded = false,
                    assetPath = assetPath,
                    Asset = AssetDatabase.LoadMainAssetAtPath(assetPath),
                    subAssets = new List<SubAssetSelection>()
                };
            }

            selectionHistory.Insert(0, selection); // Selection is now first in list

            var subAssetSelection = new SubAssetSelection {
                subAsset = selectedObject,
                subAssetPath = selectedObject.name
            };
            for(var i = 0; i < selection.subAssets.Count; i++) {
                if(selection.subAssets[i].subAsset == selectedObject ||
                    // In case of unloading and reloading a scene, we try to "reuse" entries to the same object.
                    // If several objects exist by the same name, they're already null and become a new entry "anyway"
                    (selection.subAssets[i].subAsset == null && selection.subAssets[i].subAssetPath == subAssetSelection.subAssetPath)) {
                    selection.subAssets.RemoveAt(i);
                    break;
                }
            }
            selection.subAssets.Insert(0, subAssetSelection);

            // while instead of if, since you can change the number of tracked assets
            while(selectionHistory.Count > maxHistory) {
                selectionHistory.RemoveAt(selectionHistory.Count - 1);
            }
            UpdatePinned();
            Repaint();
        }

        private void UpdatePinned() {
            var numberOfAlreadyPinnedEntries = 0;
            for(var i = 0; i < selectionHistory.Count; i++) {
                var currSelection = selectionHistory[i];
                if(currSelection.isPinned) {
                    if(i == numberOfAlreadyPinnedEntries) {
                        numberOfAlreadyPinnedEntries++;
                        continue;
                    }
                    selectionHistory.RemoveAt(i);
                    selectionHistory.Insert(numberOfAlreadyPinnedEntries, currSelection);
                    numberOfAlreadyPinnedEntries++;
                }
            }
        }

        private void OnGUI() {
            var originalIconSize = EditorGUIUtility.GetIconSize();
            EditorGUIUtility.SetIconSize(new Vector2(16f, 16f));
            if(guiStyle == null) {
                guiStyle = new GUIStyle(GUI.skin.button) {
                    alignment = TextAnchor.MiddleLeft
                };
            }
            EditorGUILayout.BeginHorizontal();
            TightLabel("History count", "The number of recent assets currently tracked");
            TightLabel(selectionHistory.Count.ToString());
            if(GUILayout.Button("Clear", GUILayout.ExpandWidth(false))) {
                selectionHistory.Clear();
            }
            GUILayout.FlexibleSpace();
            TightLabel("What to track", "Select for what interactions you want this list to be updated");
            var onSelection = GUILayout.Toggle(subscribeToSelectionEvents, "On selection", GUILayout.ExpandWidth(false));
            if(onSelection != subscribeToSelectionEvents) {
                SubscribeToSelectionEvents = onSelection;
            }
            EditorGUI.BeginDisabledGroup(subscribeToSelectionEvents); // If we're listening to selection the other two doesn't matter
            subscribeToPrefabOpenEvents = GUILayout.Toggle(subscribeToPrefabOpenEvents, "Open prefabs", GUILayout.ExpandWidth(false));
            SubscribeToSceneOpenEvents = GUILayout.Toggle(subscribeToSceneOpenEvents, "Open scenes", GUILayout.ExpandWidth(false));
            EditorGUI.EndDisabledGroup();
            GUILayout.FlexibleSpace();
            TightLabel("Max history", "The number of recent assets to track");
            maxHistory = EditorGUILayout.DelayedIntField(maxHistory, GUILayout.Width(50f));
            GUILayout.FlexibleSpace();
            EditorGUILayout.EndHorizontal();
            scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
            var pinStatusChanged = false;
            for(var i = 0; i < selectionHistory.Count; i++) {
                var selection = selectionHistory[i];

                EditorGUILayout.BeginHorizontal();

                // Pinning
                var pinIcon = selection.isPinned ? PinnedIcon : UnPinnedIcon;
                if(GUILayout.Button(EditorGUIUtility.IconContent(pinIcon), GUILayout.Width(20f), GUILayout.ExpandWidth(false))) {
                    selection.isPinned = !selection.isPinned;
                    pinStatusChanged = true;
                }

                // Open
                if(GUILayout.Button(EditorGUIUtility.IconContent("d_editicon.sml"), GUILayout.Width(26f), GUILayout.ExpandWidth(false))) {
                    if(Event.current.button == 1) {
                        // On right click we only ping the object.
                        EditorGUIUtility.PingObject(selection.Asset);
                    }
                    else {
                        AssetDatabase.OpenAsset(selection.Asset);
                    }
                }

                // Object
                GUI.enabled = false;
                if(selection.Asset != null) {
                    EditorGUILayout.ObjectField(selection.Asset, typeof(Object), false);
                }
                else {
                    var labelNotFound = new GUIContent($"{selection.FileName} not found.",
                        EditorGUIUtility.ObjectContent(selection.Asset, typeof(Object)).image, selection.assetPath);
                    EditorGUILayout.LabelField(labelNotFound);
                }
                GUI.enabled = true;

                // Sub entries
                GUILayout.Label(GUIContent.none, GUILayout.MinWidth(80f), GUILayout.MaxWidth(80f));
                var rect = GUILayoutUtility.GetLastRect();
                selection.expanded = EditorGUI.Foldout(rect, selection.expanded, "Sub entries", true);
                EditorGUILayout.EndHorizontal();
                if(selection.expanded) {
                    EditorGUILayout.BeginVertical();
                    EditorGUI.indentLevel++;
                    for(var y = 0; y < selection.subAssets.Count; y++) {
                        var subAsset = selection.subAssets[y];
                        EditorGUILayout.BeginHorizontal();
                        EditorGUILayout.Space(10f, false); // As an indent
                        var label = new GUIContent(subAsset.subAssetPath, EditorGUIUtility.ObjectContent(subAsset.subAsset, typeof(Object)).image);
                        if(GUILayout.Button(label, guiStyle, GUILayout.MinWidth(50f), GUILayout.MaxWidth(200f), GUILayout.ExpandWidth(false))) {
                            if(Event.current.button == 1) {
                                // On right click we only ping the object.
                                EditorGUIUtility.PingObject(subAsset.subAsset);
                            }
                            else {
                                Selection.activeObject = subAsset.subAsset;
                            }
                        }
                        EditorGUILayout.EndHorizontal();
                    }
                    EditorGUILayout.EndVertical();
                    EditorGUI.indentLevel--;
                }
            }
            EditorGUILayout.EndScrollView();
            EditorGUIUtility.SetIconSize(originalIconSize);
            if(pinStatusChanged) {
                UpdatePinned();
            }
        }

        public static void TightLabel(string labelStr) {
            var label = new GUIContent(labelStr);
            TightLabel(label);
        }

        public static void TightLabel(string labelStr, string tooltip) {
            var label = new GUIContent(labelStr, tooltip);
            TightLabel(label);
        }

        public static void TightLabel(GUIContent label) {
            //This is the important bit, we set the width to the calculated width of the content in the GUIStyle of the control
            EditorGUILayout.LabelField(label, GUILayout.Width(GUI.skin.label.CalcSize(label).x));
        }
    }
}
