using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;

namespace Jovian.TagSystem.Editor {

    public class JovianTagsEditorWindow : EditorWindow {

        // --- Colors ---
        private static readonly Color DividerColor = new(1f, 1f, 1f, 0.12f);
        private static readonly Color GreenButton = new(0.3f, 0.8f, 0.3f);
        private static readonly Color HeaderBg = new(0.18f, 0.18f, 0.18f, 1f);
        private static readonly Color RowEven = new(0f, 0f, 0f, 0f);
        private static readonly Color RowOdd = new(1f, 1f, 1f, 0.03f);
        private static readonly Color RowHover = new(0.3f, 0.5f, 0.8f, 0.12f);
        private static readonly Color ChildIndentLine = new(1f, 1f, 1f, 0.08f);

        // --- Constants ---
        private const int RowHeight = 22;
        private const int HeaderHeight = 26;
        private const int DepthIndent = 20;

        // --- Cached layout options ---
        private static readonly GUILayoutOption RowHeightOpt = GUILayout.Height(RowHeight);
        private static readonly GUILayoutOption HeaderHeightOpt = GUILayout.Height(HeaderHeight);
        private static readonly GUILayoutOption ExpandWidth = GUILayout.ExpandWidth(true);
        private static readonly GUILayoutOption SmallBtnWidth = GUILayout.Width(22);
        private static readonly GUILayoutOption SmallBtnHeight = GUILayout.Height(18);

        // --- State ---
        private Vector2 scrollPosition;
        private string searchFilter = "";
        private string newTagName = "";
        private string outputPath;
        private readonly Dictionary<string, bool> foldoutState = new();
        private bool scrollToBottom;
        private JovianTagsSettings[] allSettings;
        private int rowIndex;

        // --- Styles (lazy init) ---
        private GUIStyle _tagStyle;

        private GUIStyle TagStyle => _tagStyle ??= new GUIStyle(EditorStyles.label) {
            fontSize = 12, fixedHeight = RowHeight, padding = new RectOffset(2, 2, 2, 2)
        };

        private GUIStyle _headerStyle;

        private GUIStyle HeaderStyle => _headerStyle ??= new GUIStyle(EditorStyles.boldLabel) {
            fontSize = 12, fixedHeight = HeaderHeight, padding = new RectOffset(2, 2, 4, 4)
        };

        private GUIStyle _pathStyle;

        private GUIStyle PathStyle => _pathStyle ??= new GUIStyle(EditorStyles.miniLabel) {
            fontSize = 10, fixedHeight = RowHeight,
            normal = { textColor = new Color(1f, 1f, 1f, 0.3f) },
            padding = new RectOffset(4, 4, 4, 2)
        };

        // --- Tree structure ---
        private class TagTreeNode {
            public string Name; // segment name (e.g., "Fire")
            public string FullPath; // full dotted path (e.g., "Damage.Fire")
            public bool IsRegistered; // exists in GameTagSettings
            public List<TagTreeNode> Children = new();
        }

        [MenuItem("Jovian/Tag System/Tag Editor...")]
        public static void ShowWindow() {
            var window = GetWindow<JovianTagsEditorWindow>(true, "Tag Editor");
            window.minSize = new Vector2(480, 380);
            window.Show();
        }

        private void OnEnable() { RefreshData(); }
        private void OnFocus() { RefreshData(); }

        private void RefreshData() {
            allSettings = JovianTagsEditorUtility.GetSettings();
            outputPath = JovianTagsGenerator.FindOrDefaultOutputPath();
        }

        private void OnGUI() {
            EditorGUILayout.BeginHorizontal();
            GUILayout.Space(8);
            EditorGUILayout.BeginVertical();
            GUILayout.Space(8);

            DrawToolbar();

            if(scrollToBottom) {
                scrollPosition.y = float.MaxValue;
                scrollToBottom = false;
            }

            scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, GUIStyle.none, GUI.skin.verticalScrollbar);
            DrawContent();
            EditorGUILayout.EndScrollView();

            DrawDivider();
            DrawAddTagRow();
            GUILayout.Space(4);
            DrawFooter();

            GUILayout.Space(8);
            EditorGUILayout.EndVertical();
            GUILayout.Space(8);
            EditorGUILayout.EndHorizontal();
        }

        // ===== Toolbar =====

        private void DrawToolbar() {
            EditorGUILayout.BeginHorizontal();
            GUILayout.Label("Output:", GUILayout.Width(50));
            EditorGUI.BeginDisabledGroup(true);
            EditorGUILayout.TextField(outputPath);
            EditorGUI.EndDisabledGroup();
            if(GUILayout.Button("...", GUILayout.Width(26), GUILayout.Height(18)))
                ChangeOutputPath();
            EditorGUILayout.EndHorizontal();

            GUILayout.Space(2);

            EditorGUILayout.BeginHorizontal();
            GUILayout.Label("Search:", GUILayout.Width(50));
            searchFilter = EditorGUILayout.TextField(searchFilter);
            if(!string.IsNullOrEmpty(searchFilter) && GUILayout.Button("✕", GUILayout.Width(20), GUILayout.Height(18))) {
                searchFilter = "";
                GUI.FocusControl(null);
            }
            EditorGUILayout.EndHorizontal();

            GUILayout.Space(4);
            DrawDivider();
        }

        // ===== Content =====

        private void DrawContent() {
            if(allSettings.Length == 0) {
                GUILayout.Space(40);
                DrawCenteredMessage("No Tag Settings asset found.");
                GUILayout.Space(8);
                EditorGUILayout.BeginHorizontal();
                GUILayout.FlexibleSpace();
                var prevBg = GUI.backgroundColor;
                GUI.backgroundColor = GreenButton;
                if(GUILayout.Button("Create Tag Settings", GUILayout.Height(28), GUILayout.Width(180))) {
                    CreateSettingsAsset();
                }
                GUI.backgroundColor = prevBg;
                GUILayout.FlexibleSpace();
                EditorGUILayout.EndHorizontal();
                return;
            }

            var allTags = CollectAllTags();
            if(allTags.Count == 0) {
                GUILayout.Space(40);
                DrawCenteredMessage("No tags defined.\nAdd tags using the field below.");
                return;
            }

            // Build tree
            var roots = BuildTree(allTags);

            // Filter
            if(!string.IsNullOrEmpty(searchFilter)) {
                roots = FilterTree(roots, searchFilter);
            }

            rowIndex = 0;
            foreach(var root in roots.OrderBy(r => r.Name, System.StringComparer.OrdinalIgnoreCase)) {
                DrawTreeNode(root, 0);
            }

            GUILayout.Space(8);
            var totalTags = allSettings.Sum(s => s.gameTags.Length);
            GUILayout.Label($"{totalTags} tags total", EditorStyles.centeredGreyMiniLabel);
        }

        private void DrawTreeNode(TagTreeNode node, int depth) {
            var hasChildren = node.Children.Count > 0;
            var isRoot = depth == 0;

            // --- Row ---
            if(isRoot) {
                // Root header
                var headerRect = EditorGUILayout.BeginHorizontal(HeaderHeightOpt);
                EditorGUI.DrawRect(headerRect, HeaderBg);
                GUILayout.Space(6);

                if(hasChildren) {
                    foldoutState.TryAdd(node.FullPath, true);
                    foldoutState[node.FullPath] = EditorGUILayout.Foldout(
                        foldoutState[node.FullPath], "", true, EditorStyles.foldout);
                    GUILayout.Space(-4);
                    GUILayout.Label(node.Name, HeaderStyle, ExpandWidth);
                    GUILayout.Label($"{CountDescendants(node)}", new GUIStyle(EditorStyles.miniLabel) {
                        alignment = TextAnchor.MiddleRight,
                        normal = { textColor = new Color(1, 1, 1, 0.4f) }
                    }, GUILayout.Width(30));
                }
                else {
                    GUILayout.Space(14);
                    GUILayout.Label(node.Name, HeaderStyle, ExpandWidth);
                }

                DrawAddChildButton(node);
                DrawDeleteButton(node.FullPath);
                GUILayout.Space(4);
                EditorGUILayout.EndHorizontal();
            }
            else {
                // Child row
                var bgColor = rowIndex % 2 == 0 ? RowEven : RowOdd;
                var rect = EditorGUILayout.BeginHorizontal(RowHeightOpt);
                if(rect.Contains(Event.current.mousePosition)) bgColor = RowHover;
                if(bgColor.a > 0) EditorGUI.DrawRect(rect, bgColor);

                // Indent with vertical guide lines
                GUILayout.Space(6 + depth * DepthIndent);
                if(Event.current.type == EventType.Repaint) {
                    for(int d = 1; d <= depth; d++) {
                        var lineX = 6 + d * DepthIndent - 10;
                        EditorGUI.DrawRect(new Rect(lineX, rect.y, 1, rect.height), ChildIndentLine);
                    }
                }

                if(hasChildren) {
                    foldoutState.TryAdd(node.FullPath, true);
                    foldoutState[node.FullPath] = EditorGUILayout.Foldout(
                        foldoutState[node.FullPath], "", true, EditorStyles.foldout);
                    GUILayout.Space(-4);
                }
                else {
                    GUILayout.Space(14);
                }

                GUILayout.Label(node.Name, TagStyle, ExpandWidth);

                // Full path hint for deep tags
                if(depth > 1) {
                    GUILayout.Label(node.FullPath, PathStyle,
                        GUILayout.Width(Mathf.Min(250, position.width * 0.3f)));
                }

                DrawAddChildButton(node);
                DrawDeleteButton(node.FullPath);
                GUILayout.Space(4);
                EditorGUILayout.EndHorizontal();
                rowIndex++;
            }

            // --- Children ---
            if(!hasChildren) return;
            foldoutState.TryAdd(node.FullPath, true);
            if(!foldoutState[node.FullPath]) return;

            foreach(var child in node.Children.OrderBy(c => c.Name, System.StringComparer.OrdinalIgnoreCase)) {
                DrawTreeNode(child, depth + 1);
            }
        }

        private void DrawAddChildButton(TagTreeNode parent) {
            var prevBg = GUI.backgroundColor;
            GUI.backgroundColor = GreenButton;
            if(GUILayout.Button("+", SmallBtnWidth, SmallBtnHeight)) {
                // Open a small input for the child name
                AddChildTagPopup.Show(parent.FullPath, childName => {
                    var fullTag = parent.FullPath + "." + childName;
                    AddTag(fullTag);
                });
            }
            GUI.backgroundColor = prevBg;
        }

        private void DrawDeleteButton(string tag) {
            var prevBg = GUI.backgroundColor;
            GUI.backgroundColor = new Color(0.8f, 0.2f, 0.2f);
            if(GUILayout.Button("✕", SmallBtnWidth, SmallBtnHeight)) {
                DeleteTag(tag);
            }
            GUI.backgroundColor = prevBg;
        }

        // ===== Add Tag Row =====

        private void DrawAddTagRow() {
            GUILayout.Space(4);
            EditorGUILayout.BeginHorizontal();
            GUILayout.Label("New Tag:", GUILayout.Width(60));
            newTagName = EditorGUILayout.TextField(newTagName, GUILayout.Height(20));

            string validationError = null;
            if(!string.IsNullOrWhiteSpace(newTagName)) {
                validationError = JovianTagsGenerator.ValidateTagName(newTagName.Trim());
                if(validationError == null && CollectAllTags().Contains(newTagName.Trim()))
                    validationError = $"Tag '{newTagName.Trim()}' already exists.";
            }
            var canAdd = !string.IsNullOrWhiteSpace(newTagName) && validationError == null;

            EditorGUI.BeginDisabledGroup(!canAdd);
            var prevBg = GUI.backgroundColor;
            GUI.backgroundColor = GreenButton;
            if(GUILayout.Button("+ Add", GUILayout.Width(60), GUILayout.Height(20))) {
                AddTag(newTagName.Trim());
                newTagName = "";
                GUI.FocusControl(null);
                scrollToBottom = true;
            }
            GUI.backgroundColor = prevBg;
            EditorGUI.EndDisabledGroup();
            EditorGUILayout.EndHorizontal();

            if(!string.IsNullOrWhiteSpace(newTagName) && validationError != null)
                EditorGUILayout.HelpBox(validationError, MessageType.Error);
            if(canAdd && newTagName.Contains(JovianTagsHandler.tagDelimiter))
                EditorGUILayout.HelpBox("Parent tags are created automatically.", MessageType.Info);

            GUILayout.Space(4);
        }

        // ===== Footer =====

        private void DrawFooter() {
            EditorGUILayout.BeginHorizontal();
            var prevBg = GUI.backgroundColor;
            GUI.backgroundColor = GreenButton;
            if(GUILayout.Button("Save & Generate", GUILayout.Height(28), GUILayout.MinWidth(150)))
                SaveAndGenerate();
            GUI.backgroundColor = prevBg;
            if(GUILayout.Button("Clean Deleted In Assets", GUILayout.Height(28))) {
                CleanStaleTagsFromAllAssets();
            }
            if(GUILayout.Button("Refresh", GUILayout.Height(28), GUILayout.Width(70))) {
                RefreshData();
                Repaint();
            }
            EditorGUILayout.EndHorizontal();
        }

        // ===== Tree Building =====

        private List<TagTreeNode> BuildTree(List<string> allTags) {
            var rootNodes = new Dictionary<string, TagTreeNode>();
            var allNodes = new Dictionary<string, TagTreeNode>();

            foreach(var tag in allTags) {
                var parts = tag.Split(JovianTagsHandler.tagDelimiter);
                var accumulated = "";
                TagTreeNode parent = null;

                for(int i = 0; i < parts.Length; i++) {
                    accumulated = i == 0 ? parts[i] : accumulated + "." + parts[i];

                    if(!allNodes.TryGetValue(accumulated, out var node)) {
                        node = new TagTreeNode {
                            Name = parts[i],
                            FullPath = accumulated,
                            IsRegistered = allTags.Contains(accumulated)
                        };
                        allNodes[accumulated] = node;

                        if(parent != null)
                            parent.Children.Add(node);
                        else
                            rootNodes[accumulated] = node;
                    }
                    parent = node;
                }
            }

            return rootNodes.Values.ToList();
        }

        private List<TagTreeNode> FilterTree(List<TagTreeNode> roots, string filter) {
            var result = new List<TagTreeNode>();
            foreach(var root in roots) {
                var filtered = FilterNode(root, filter);
                if(filtered != null) result.Add(filtered);
            }
            return result;
        }

        private TagTreeNode FilterNode(TagTreeNode node, string filter) {
            var matchesSelf = node.FullPath.IndexOf(filter, System.StringComparison.OrdinalIgnoreCase) >= 0;
            var filteredChildren = new List<TagTreeNode>();

            foreach(var child in node.Children) {
                var fc = FilterNode(child, filter);
                if(fc != null) filteredChildren.Add(fc);
            }

            if(!matchesSelf && filteredChildren.Count == 0) return null;

            return new TagTreeNode {
                Name = node.Name,
                FullPath = node.FullPath,
                IsRegistered = node.IsRegistered,
                Children = matchesSelf ? node.Children : filteredChildren
            };
        }

        private int CountDescendants(TagTreeNode node) {
            int count = 0;
            foreach(var child in node.Children) {
                count++;
                count += CountDescendants(child);
            }
            return count;
        }

        // ===== Actions =====

        private void AddTag(string tagName) {
            if(allSettings.Length == 0) {
                EditorUtility.DisplayDialog("No Settings", "Create a JovianTagsSettings asset first.", "OK");
                return;
            }
            Undo.RecordObject(allSettings[0], "Add Tag");
            allSettings[0].AddGameTag(tagName);
        }

        private void DeleteTag(string tag) {
            foreach(var s in allSettings) {
                if(!s.gameTags.Any(t => t.tag == tag)) continue;
                var hasChildren = s.gameTags.Any(t => t.tag != tag && t.tag.StartsWith(tag + "."));
                var message = hasChildren ? $"Delete '{tag}' and all children?" : $"Delete '{tag}'?";
                if(EditorUtility.DisplayDialog("Delete Tag", message, "Delete", "Cancel")) {
                    Undo.RecordObject(s, "Remove Tag");
                    s.RemoveTag(tag);
                    GUIUtility.ExitGUI();
                }
                return;
            }
        }

        private void CreateSettingsAsset() {
            var folder = EditorUtility.OpenFolderPanel("Choose folder for Tag Settings", "Assets", "");
            if(string.IsNullOrEmpty(folder)) return;

            var dataPath = Application.dataPath.Replace('\\', '/');
            folder = folder.Replace('\\', '/');
            if(folder.StartsWith(dataPath))
                folder = "Assets" + folder.Substring(dataPath.Length);

            var asset = ScriptableObject.CreateInstance<JovianTagsSettings>();
            var path = $"{folder}/JovianTagsSettings.asset";
            AssetDatabase.CreateAsset(asset, path);
            AssetDatabase.SaveAssets();
            EditorUtility.FocusProjectWindow();
            Selection.activeObject = asset;
            Debug.Log($"[Tag System] Created Tag Settings at {path}");
            RefreshData();
        }

        private void CleanStaleTagsFromAllAssets() {
            var validTags = new HashSet<string>(System.StringComparer.Ordinal);
            foreach(var s in allSettings)
            foreach(var t in s.gameTags)
                if(!string.IsNullOrEmpty(t.tag))
                    validTags.Add(t.tag);

            int assetsScanned = 0;
            int assetsCleaned = 0;
            int tagsRemoved = 0;

            // Scan all prefabs and ScriptableObjects
            var guids = AssetDatabase.FindAssets("t:GameObject t:ScriptableObject");
            // FindAssets with multiple types uses OR, but let's do them separately for clarity
            var prefabGuids = AssetDatabase.FindAssets("t:Prefab");
            var soGuids = AssetDatabase.FindAssets("t:ScriptableObject");
            var allGuids = new HashSet<string>(prefabGuids);
            foreach(var g in soGuids) allGuids.Add(g);

            foreach(var guid in allGuids) {
                var path = AssetDatabase.GUIDToAssetPath(guid);
                var assets = AssetDatabase.LoadAllAssetsAtPath(path);
                assetsScanned++;

                foreach(var asset in assets) {
                    if(asset == null) continue;
                    var so = new SerializedObject(asset);
                    var cleaned = CleanSerializedObject(so, validTags);
                    if(cleaned > 0) {
                        so.ApplyModifiedPropertiesWithoutUndo();
                        EditorUtility.SetDirty(asset);
                        assetsCleaned++;
                        tagsRemoved += cleaned;
                    }
                }
            }

            AssetDatabase.SaveAssets();

            EditorUtility.DisplayDialog("Clean Complete",
                $"Scanned {assetsScanned} assets.\n" +
                $"Cleaned {assetsCleaned} assets.\n" +
                $"Removed {tagsRemoved} stale tag references.",
                "OK");
        }

        private static int CleanSerializedObject(SerializedObject so, HashSet<string> validTags) {
            int totalRemoved = 0;
            var prop = so.GetIterator();

            while(prop.NextVisible(true)) {
                // Look for string arrays named "tags" inside JovianTagsGroup structs
                if(prop.isArray && prop.propertyType == SerializedPropertyType.String) continue;
                if(prop.propertyType != SerializedPropertyType.Generic) continue;
                if(!prop.type.Contains("JovianTagsGroup")) continue;

                var tagsProp = prop.FindPropertyRelative("tags");
                if(tagsProp == null || !tagsProp.isArray) continue;

                for(int i = tagsProp.arraySize - 1; i >= 0; i--) {
                    var val = tagsProp.GetArrayElementAtIndex(i).stringValue;
                    if(!string.IsNullOrEmpty(val) && !validTags.Contains(val)) {
                        tagsProp.DeleteArrayElementAtIndex(i);
                        totalRemoved++;
                    }
                }
            }

            return totalRemoved;
        }

        private void SaveAndGenerate() {
            foreach(var s in allSettings) EditorUtility.SetDirty(s);
            AssetDatabase.SaveAssets();
            JovianTagsGenerator.Regenerate(outputPath);
        }

        private void ChangeOutputPath() {
            var newPath = EditorUtility.SaveFilePanel("Choose Generated File Location",
                System.IO.Path.GetDirectoryName(outputPath) ?? "Assets",
                System.IO.Path.GetFileName(outputPath) ?? "GameTags.cs", "cs");
            if(string.IsNullOrEmpty(newPath)) return;
            var dataPath = Application.dataPath.Replace('\\', '/');
            newPath = newPath.Replace('\\', '/');
            if(newPath.StartsWith(dataPath))
                newPath = "Assets" + newPath.Substring(dataPath.Length);
            outputPath = newPath;
        }

        // ===== Helpers =====

        private List<string> CollectAllTags() {
            var tags = new SortedSet<string>(System.StringComparer.OrdinalIgnoreCase);
            foreach(var s in allSettings)
            foreach(var t in s.gameTags)
                if(!string.IsNullOrEmpty(t.tag))
                    tags.Add(t.tag);
            return tags.ToList();
        }

        private static void DrawDivider() {
            var rect = EditorGUILayout.GetControlRect(false, 1);
            EditorGUI.DrawRect(rect, DividerColor);
        }

        private static void DrawCenteredMessage(string message) {
            var style = new GUIStyle(EditorStyles.label) {
                alignment = TextAnchor.MiddleCenter, wordWrap = true, fontSize = 12,
                normal = { textColor = new Color(1, 1, 1, 0.4f) }
            };
            EditorGUILayout.LabelField(message, style, GUILayout.Height(60));
        }
    }

    // ===== Add Child Tag Popup =====

    public class AddChildTagPopup : EditorWindow {
        private string parentPath;
        private string childName = "";
        private System.Action<string> onConfirm;

        public static void Show(string parentPath, System.Action<string> onConfirm) {
            var popup = CreateInstance<AddChildTagPopup>();
            popup.parentPath = parentPath;
            popup.onConfirm = onConfirm;
            popup.titleContent = new GUIContent("Add Child Tag");
            var size = new Vector2(300, 90);
            var mousePos = GUIUtility.GUIToScreenPoint(Event.current.mousePosition);
            popup.position = new Rect(mousePos.x - size.x * 0.5f, mousePos.y, size.x, size.y);
            popup.ShowUtility();
            popup.Focus();
        }

        private void OnGUI() {
            GUILayout.Space(8);
            GUILayout.Label($"Parent: {parentPath}", EditorStyles.boldLabel);
            GUILayout.Space(4);

            EditorGUILayout.BeginHorizontal();
            GUILayout.Label("Name:", GUILayout.Width(42));
            GUI.SetNextControlName("ChildNameField");
            childName = EditorGUILayout.TextField(childName);
            EditorGUILayout.EndHorizontal();

            EditorGUI.FocusTextInControl("ChildNameField");

            var error = string.IsNullOrWhiteSpace(childName)
                ? null
                : JovianTagsGenerator.ValidateTagSegment(childName.Trim());
            var canAdd = !string.IsNullOrWhiteSpace(childName) && error == null;

            GUILayout.Space(4);

            EditorGUILayout.BeginHorizontal();
            EditorGUI.BeginDisabledGroup(!canAdd);
            if(GUILayout.Button("Add", GUILayout.Height(22)) || (canAdd && Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Return)) {
                onConfirm?.Invoke(childName.Trim());
                Close();
            }
            EditorGUI.EndDisabledGroup();
            if(GUILayout.Button("Cancel", GUILayout.Height(22))) {
                Close();
            }
            EditorGUILayout.EndHorizontal();

            if(Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Escape)
                Close();
        }
    }
}
