using System;
using System.Collections.Generic;
using System.Linq;
using Jovian.EncounterSystem;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;

namespace Jovian.EncounterSystem.Editor {
    /// <summary>Browser for every encounter across all tables. Search + kind filter + detail pane. Quest chains render as a tree rooted at the first step.</summary>
    public class EncounterBrowserWindow : EditorWindow {
        private const string AllKinds = "All";

        private class Record {
            public EncounterTable table;
            public int index;
            public IEncounter encounter;
            public int depth;

            public bool IsTableHeader => encounter == null;
        }

        private static readonly Color[] DepthColors = {
            new(0.35f, 0.55f, 0.85f), // depth 0 — table headers (blue)
            new(0.90f, 0.90f, 0.90f), // depth 1 — encounters under table (neutral)
            new(0.70f, 0.90f, 0.55f), // depth 2 — chain step 1 (green)
            new(0.95f, 0.80f, 0.45f), // depth 3 — chain step 2 (amber)
            new(0.85f, 0.65f, 0.90f)  // depth 4+ — deeper chain (violet)
        };

        private readonly List<Record> allRecords = new();
        private string searchText = string.Empty;
        private string kindFilter = AllKinds;

        private readonly Dictionary<IEncounter, List<ValidationIssue>> issuesByEncounter = new();

        private TreeView treeView;
        private VisualElement detailPane;
        private ToolbarMenu kindDropdown;
        private VisualElement statusBanner;

        [MenuItem("Jovian/Encounters/Encounter Browser")]
        public static void Open() {
            var window = GetWindow<EncounterBrowserWindow>("Encounters");
            window.minSize = new Vector2(640, 360);
        }

        private void CreateGUI() {
            BuildToolbar();
            BuildSplit();
            Refresh();
        }

        private void BuildToolbar() {
            var toolbar = new Toolbar();

            var search = new ToolbarSearchField();
            search.style.flexGrow = 1f;
            search.RegisterValueChangedCallback(evt => {
                searchText = evt.newValue ?? string.Empty;
                ApplyFilter();
            });
            toolbar.Add(search);

            kindDropdown = new ToolbarMenu { text = $"Kind: {AllKinds}" };
            foreach(var choice in GetKindChoices()) {
                var captured = choice;
                kindDropdown.menu.AppendAction(captured, _ => {
                    kindFilter = captured;
                    kindDropdown.text = $"Kind: {captured}";
                    ApplyFilter();
                });
            }
            toolbar.Add(kindDropdown);

            var refreshButton = new ToolbarButton(Refresh) { text = "Refresh" };
            toolbar.Add(refreshButton);

            rootVisualElement.Add(toolbar);
        }

        private void BuildSplit() {
            statusBanner = new VisualElement {
                style = {
                    paddingLeft = 10,
                    paddingRight = 10,
                    paddingTop = 8,
                    paddingBottom = 8,
                    marginBottom = 2,
                    flexDirection = FlexDirection.Column,
                    backgroundColor = new StyleColor(new Color(0.85f, 0.4f, 0.15f, 0.35f)),
                    display = DisplayStyle.None
                }
            };
            rootVisualElement.Add(statusBanner);

            var split = new VisualElement {
                style = {
                    flexDirection = FlexDirection.Row,
                    flexGrow = 1f
                }
            };
            rootVisualElement.Add(split);

            treeView = new TreeView {
                makeItem = MakeRow,
                bindItem = BindRow,
                fixedItemHeight = 22,
                selectionType = SelectionType.Single
            };
            treeView.selectionChanged += OnSelectionChanged;
            treeView.style.width = 280;
            treeView.style.flexShrink = 0f;
            treeView.style.borderRightWidth = 1;
            treeView.style.borderRightColor = new StyleColor(new Color(0f, 0f, 0f, 0.4f));
            split.Add(treeView);

            detailPane = new ScrollView(ScrollViewMode.Vertical) {
                style = { paddingLeft = 8, paddingTop = 8, paddingRight = 8, flexGrow = 1f }
            };
            ShowEmptyDetail();
            split.Add(detailPane);
        }

        private static VisualElement MakeRow() {
            var row = new VisualElement {
                style = {
                    flexDirection = FlexDirection.Row,
                    alignItems = Align.Center,
                    paddingLeft = 6,
                    paddingRight = 6,
                    height = 22,
                    flexGrow = 1f,
                    flexShrink = 0f
                }
            };

            var badge = new VisualElement {
                name = "issue-badge",
                style = {
                    width = 8,
                    height = 8,
                    marginRight = 6,
                    borderTopLeftRadius = 4,
                    borderTopRightRadius = 4,
                    borderBottomLeftRadius = 4,
                    borderBottomRightRadius = 4,
                    visibility = Visibility.Hidden
                }
            };
            row.Add(badge);

            var label = new Label {
                name = "row-label",
                style = {
                    flexGrow = 1f,
                    unityTextAlign = TextAnchor.MiddleLeft
                }
            };
            row.Add(label);

            var selectButton = new Button {
                name = "select-button",
                text = "Select",
                style = {
                    marginLeft = 4,
                    marginRight = 0,
                    paddingLeft = 6,
                    paddingRight = 6,
                    display = DisplayStyle.None
                }
            };
            row.Add(selectButton);

            return row;
        }

        private void BindRow(VisualElement element, int index) {
            var record = treeView.GetItemDataForIndex<Record>(index);
            var label = element.Q<Label>("row-label");
            var badge = element.Q<VisualElement>("issue-badge");
            var selectButton = element.Q<Button>("select-button");

            var depthIndex = Mathf.Clamp(record.depth, 0, DepthColors.Length - 1);
            label.style.color = new StyleColor(DepthColors[depthIndex]);

            if(record.IsTableHeader) {
                label.text = record.table != null ? record.table.name : "<missing table>";
                label.style.unityFontStyleAndWeight = FontStyle.Bold;
                badge.style.visibility = Visibility.Hidden;
                element.tooltip = $"EncounterTable asset: {record.table?.name}";

                selectButton.style.display = DisplayStyle.Flex;
                var table = record.table;
                selectButton.clickable = new Clickable(() => {
                    if(table == null) {
                        return;
                    }
                    Selection.activeObject = table;
                    EditorGUIUtility.PingObject(table);
                });
                return;
            }

            label.style.unityFontStyleAndWeight = FontStyle.Normal;
            selectButton.style.display = DisplayStyle.None;

            var name = record.encounter?.EncounterDefinition?.name;
            var kind = record.encounter?.EncounterDefinition?.Kind?.GetType().Name ?? "—";
            label.text = string.IsNullOrEmpty(name)
                ? $"<unnamed>   [{kind}]"
                : $"{name}   [{kind}]";

            if(record.encounter != null && issuesByEncounter.TryGetValue(record.encounter, out var issues) && issues.Count > 0) {
                var hasError = issues.Exists(i => i.severity == ValidationSeverity.Error);
                badge.style.backgroundColor = new StyleColor(hasError ? new Color(0.85f, 0.25f, 0.25f) : new Color(0.95f, 0.75f, 0.1f));
                badge.style.visibility = Visibility.Visible;
                element.tooltip = BuildTooltip(issues);
            }
            else {
                badge.style.visibility = Visibility.Hidden;
                element.tooltip = string.Empty;
            }
        }

        private static string BuildTooltip(List<ValidationIssue> issues) {
            var lines = new List<string>(issues.Count);
            foreach(var issue in issues) {
                var prefix = issue.severity == ValidationSeverity.Error ? "ERROR" : "WARN";
                lines.Add($"[{prefix}] {issue.path} — {issue.message}");
            }
            return string.Join("\n", lines);
        }

        private static IEnumerable<string> GetKindChoices() {
            yield return AllKinds;

            var kindTypes = AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(assembly => {
                    try { return assembly.GetTypes(); }
                    catch { return Array.Empty<Type>(); }
                })
                .Where(type => typeof(IEncounterKind).IsAssignableFrom(type) && !type.IsAbstract && !type.IsInterface)
                .Select(type => type.Name)
                .OrderBy(name => name);

            foreach(var name in kindTypes) {
                yield return name;
            }
        }

        private void Refresh() {
            allRecords.Clear();

            var registries = FindAssetsOfType<EncounterRegistry>().ToList();
            UpdateStatusBanner(registries);

            var seenTables = new HashSet<EncounterTable>();
            foreach(var registry in registries) {
                if(registry?.encounterCollections == null) {
                    continue;
                }

                foreach(var collection in registry.encounterCollections) {
                    if(collection?.encounterTables == null) {
                        continue;
                    }

                    foreach(var table in collection.encounterTables) {
                        if(table?.encounters == null || !seenTables.Add(table)) {
                            continue;
                        }

                        for(var i = 0; i < table.encounters.Count; i++) {
                            allRecords.Add(new Record {
                                table = table,
                                index = i,
                                encounter = table.encounters[i]
                            });
                        }
                    }
                }
            }

            RebuildIssueIndex();
            ApplyFilter();
        }

        private void UpdateStatusBanner(List<EncounterRegistry> registries) {
            statusBanner.Clear();

            if(registries.Count == 0) {
                statusBanner.style.display = DisplayStyle.Flex;
                statusBanner.Add(new Label("No EncounterRegistry asset found. The browser reads encounters from the registry — without one, it has nothing to show.") {
                    style = { marginBottom = 6, whiteSpace = WhiteSpace.Normal }
                });
                statusBanner.Add(new Button(CreateRegistryInteractive) {
                    text = "Create Registry…",
                    style = { alignSelf = Align.FlexStart }
                });
                return;
            }

            var emptyRegistries = registries.FindAll(r => r?.encounterCollections == null || r.encounterCollections.Length == 0);
            if(emptyRegistries.Count == registries.Count) {
                statusBanner.style.display = DisplayStyle.Flex;
                statusBanner.Add(new Label("Registry found but no EncountersCollection is assigned to its 'encounterCollections' array.") {
                    style = { whiteSpace = WhiteSpace.Normal }
                });
                var pingButton = new Button(() => {
                    Selection.activeObject = registries[0];
                    EditorGUIUtility.PingObject(registries[0]);
                }) {
                    text = "Open Registry",
                    style = { alignSelf = Align.FlexStart, marginTop = 6 }
                };
                statusBanner.Add(pingButton);
                return;
            }

            statusBanner.style.display = DisplayStyle.None;
        }

        private void CreateRegistryInteractive() {
            var path = EditorUtility.SaveFilePanelInProject(
                "Create Encounter Registry",
                "EncounterRegistry",
                "asset",
                "Choose where to save the new EncounterRegistry asset.");
            if(string.IsNullOrEmpty(path)) {
                return;
            }

            var registry = ScriptableObject.CreateInstance<EncounterRegistry>();
            AssetDatabase.CreateAsset(registry, path);
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();

            Selection.activeObject = registry;
            EditorGUIUtility.PingObject(registry);
            Refresh();
        }

        private static IEnumerable<T> FindAssetsOfType<T>() where T : UnityEngine.Object {
            var guids = AssetDatabase.FindAssets("t:" + typeof(T).Name);
            foreach(var guid in guids) {
                var path = AssetDatabase.GUIDToAssetPath(guid);
                var asset = AssetDatabase.LoadAssetAtPath<T>(path);
                if(asset != null) {
                    yield return asset;
                }
            }
        }

        private void RebuildIssueIndex() {
            issuesByEncounter.Clear();
            var issues = EncounterValidator.ValidateProject();
            foreach(var issue in issues) {
                if(issue.encounter == null) {
                    continue;
                }
                if(!issuesByEncounter.TryGetValue(issue.encounter, out var list)) {
                    list = new List<ValidationIssue>();
                    issuesByEncounter[issue.encounter] = list;
                }
                list.Add(issue);
            }
        }

        private void ApplyFilter() {
            var filtered = allRecords.Where(Matches).ToList();
            var items = BuildTreeItems(filtered);
            if(treeView == null) {
                return;
            }

            treeView.ClearSelection();
            treeView.SetRootItems(items);
            treeView.Rebuild();
            // Expand after layout settles — ExpandAll immediately after Rebuild can no-op in some cases.
            treeView.schedule.Execute(() => treeView.ExpandAll()).ExecuteLater(0);
            ShowEmptyDetail();
        }

        private static List<TreeViewItemData<Record>> BuildTreeItems(List<Record> records) {
            // Group by table: each table becomes a top-level row, its encounters nest under it.
            var byTable = new Dictionary<EncounterTable, List<Record>>();
            var tableOrder = new List<EncounterTable>();
            foreach(var r in records) {
                if(r.table == null) {
                    continue;
                }
                if(!byTable.TryGetValue(r.table, out var list)) {
                    list = new List<Record>();
                    byTable[r.table] = list;
                    tableOrder.Add(r.table);
                }
                list.Add(r);
            }

            var result = new List<TreeViewItemData<Record>>();
            var uid = 0;
            foreach(var table in tableOrder) {
                var tableRecord = new Record { table = table, index = -1, encounter = null, depth = 0 };
                var tableId = uid++;
                var children = BuildEncounterNodes(byTable[table], 1, ref uid);
                result.Add(new TreeViewItemData<Record>(tableId, tableRecord, children));
            }
            return result;
        }

        private static List<TreeViewItemData<Record>> BuildEncounterNodes(List<Record> records, int depth, ref int uid) {
            var byEncounter = new Dictionary<IEncounter, Record>();
            foreach(var r in records) {
                if(r.encounter != null) {
                    byEncounter[r.encounter] = r;
                }
            }

            // Chain nesting only applies to chains contained within this table. Cross-table chains
            // naturally appear flat (the target encounter lives under its own table header).
            var predecessorCount = new Dictionary<IEncounter, int>();
            foreach(var r in records) {
                if(r.encounter?.EncounterDefinition?.Kind is not QuestKind quest) {
                    continue;
                }
                var next = quest.nextEncounter.Resolve();
                if(next != null && byEncounter.ContainsKey(next)) {
                    predecessorCount.TryGetValue(next, out var count);
                    predecessorCount[next] = count + 1;
                }
            }

            var parentOf = new Dictionary<IEncounter, IEncounter>();
            foreach(var r in records) {
                if(r.encounter?.EncounterDefinition?.Kind is not QuestKind quest) {
                    continue;
                }
                var next = quest.nextEncounter.Resolve();
                if(next == null || !byEncounter.ContainsKey(next)) {
                    continue;
                }
                if(predecessorCount[next] != 1) {
                    continue;
                }
                if(ForwardReaches(next, r.encounter, byEncounter)) {
                    continue;
                }
                parentOf[next] = r.encounter;
            }

            var nodes = new List<TreeViewItemData<Record>>();
            foreach(var r in records) {
                if(r.encounter != null && parentOf.ContainsKey(r.encounter)) {
                    continue;
                }

                r.depth = depth;
                var rootId = uid++;
                var children = BuildChainChildren(r, byEncounter, parentOf, depth + 1, ref uid);
                nodes.Add(new TreeViewItemData<Record>(rootId, r, children));
            }
            return nodes;
        }

        private static bool ForwardReaches(IEncounter start, IEncounter target, Dictionary<IEncounter, Record> byEncounter) {
            var current = start;
            var visited = new HashSet<IEncounter>();
            while(current?.EncounterDefinition?.Kind is QuestKind quest) {
                var next = quest.nextEncounter.Resolve();
                if(next == null || !byEncounter.ContainsKey(next)) {
                    return false;
                }
                if(next == target) {
                    return true;
                }
                if(!visited.Add(next)) {
                    return false;
                }
                current = next;
            }
            return false;
        }

        private static List<TreeViewItemData<Record>> BuildChainChildren(Record root, Dictionary<IEncounter, Record> byEncounter, Dictionary<IEncounter, IEncounter> parentOf, int depth, ref int uid) {
            var children = new List<TreeViewItemData<Record>>();
            if(root.encounter?.EncounterDefinition?.Kind is not QuestKind) {
                return children;
            }

            var current = root.encounter;
            var visited = new HashSet<IEncounter> { current };
            var currentDepth = depth;
            while(current?.EncounterDefinition?.Kind is QuestKind quest) {
                var next = quest.nextEncounter.Resolve();
                if(next == null || !visited.Add(next)) {
                    break;
                }
                if(!byEncounter.TryGetValue(next, out var nextRecord)) {
                    break;
                }
                if(!parentOf.TryGetValue(next, out var parent) || parent != current) {
                    break;
                }
                nextRecord.depth = currentDepth;
                children.Add(new TreeViewItemData<Record>(uid++, nextRecord));
                current = next;
                currentDepth++;
            }
            return children;
        }

        private bool Matches(Record record) {
            var kindName = record.encounter?.EncounterDefinition?.Kind?.GetType().Name ?? string.Empty;
            if(kindFilter != AllKinds && kindName != kindFilter) {
                return false;
            }

            if(string.IsNullOrEmpty(searchText)) {
                return true;
            }

            var needle = searchText.ToLowerInvariant();
            var definition = record.encounter?.EncounterDefinition;
            return Contains(definition?.id, needle)
                || Contains(definition?.name, needle)
                || Contains(definition?.description, needle);
        }

        private static bool Contains(string source, string needle) {
            return !string.IsNullOrEmpty(source) && source.ToLowerInvariant().Contains(needle);
        }

        private void OnSelectionChanged(IEnumerable<object> selection) {
            var record = selection.OfType<Record>().FirstOrDefault();
            if(record == null) {
                ShowEmptyDetail();
                return;
            }

            if(record.IsTableHeader) {
                ShowTableHeaderDetail(record.table);
                return;
            }

            detailPane.Clear();

            var serializedObject = new SerializedObject(record.table);
            var encountersProp = serializedObject.FindProperty(nameof(EncounterTable.encounters));
            var elementProp = encountersProp.GetArrayElementAtIndex(record.index);

            var headerRow = new VisualElement {
                style = {
                    flexDirection = FlexDirection.Row,
                    alignItems = Align.Center,
                    marginBottom = 6
                }
            };
            headerRow.Add(new Label($"{record.table.name}  →  [{record.index}]") {
                style = {
                    unityFontStyleAndWeight = FontStyle.Bold,
                    flexGrow = 1f,
                    color = new StyleColor(new Color(0.75f, 0.75f, 0.75f))
                }
            });
            var pingButton = new Button(() => {
                Selection.activeObject = record.table;
                EditorGUIUtility.PingObject(record.table);
            }) {
                text = "Select Table",
                tooltip = "Select and ping the owning EncounterTable asset in the Project window."
            };
            headerRow.Add(pingButton);
            detailPane.Add(headerRow);

            elementProp.isExpanded = true;
            var field = new PropertyField(elementProp);
            field.Bind(serializedObject);
            detailPane.Add(field);

            AddChainPreviewIfQuest(record);
        }

        private void AddChainPreviewIfQuest(Record record) {
            if(record.encounter?.EncounterDefinition?.Kind is not QuestKind questKind) {
                return;
            }

            var predecessor = FindPredecessor(record.encounter.EncounterDefinition?.internalId);
            var next = questKind.nextEncounter.Resolve();

            var predName = predecessor?.encounter.EncounterDefinition?.name ?? "—";
            var currName = record.encounter.EncounterDefinition?.name ?? "<unnamed>";
            var nextName = next?.EncounterDefinition?.name ?? "—";

            var chainLabel = new Label($"Chain:  {predName}  ←  {currName}  →  {nextName}") {
                style = {
                    marginTop = 12,
                    paddingTop = 6,
                    paddingBottom = 6,
                    paddingLeft = 6,
                    paddingRight = 6,
                    backgroundColor = new StyleColor(new Color(0.2f, 0.2f, 0.2f, 0.4f)),
                    unityFontStyleAndWeight = FontStyle.Bold
                }
            };
            detailPane.Add(chainLabel);
        }

        private Record FindPredecessor(string targetId) {
            if(string.IsNullOrEmpty(targetId)) {
                return null;
            }

            foreach(var record in allRecords) {
                if(record.encounter?.EncounterDefinition?.Kind is not QuestKind kind) {
                    continue;
                }

                var next = kind.nextEncounter.Resolve();
                if(next?.EncounterDefinition?.internalId == targetId) {
                    return record;
                }
            }
            return null;
        }

        private void ShowTableHeaderDetail(EncounterTable table) {
            detailPane.Clear();
            if(table == null) {
                ShowEmptyDetail();
                return;
            }

            var headerRow = new VisualElement {
                style = {
                    flexDirection = FlexDirection.Row,
                    alignItems = Align.Center,
                    marginBottom = 6
                }
            };
            headerRow.Add(new Label(table.name) {
                style = {
                    unityFontStyleAndWeight = FontStyle.Bold,
                    flexGrow = 1f
                }
            });
            headerRow.Add(new Button(() => {
                Selection.activeObject = table;
                EditorGUIUtility.PingObject(table);
            }) { text = "Select Table" });
            detailPane.Add(headerRow);

            var count = table.encounters?.Count ?? 0;
            detailPane.Add(new Label($"{count} encounter(s). Expand the row to browse them, or pick one to edit.") {
                style = { color = new StyleColor(Color.gray) }
            });
        }

        private void ShowEmptyDetail() {
            if(detailPane == null) {
                return;
            }

            detailPane.Clear();
            var empty = new Label("Select an encounter to edit.") {
                style = { color = new StyleColor(Color.gray), marginTop = 8 }
            };
            detailPane.Add(empty);
        }
    }
}
