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

namespace Jovian.TagSystem.Editor {
    /// <summary>
    /// Generates a C# file with static GameTag fields organized in a nested class hierarchy.
    /// Fields are resolved at runtime via JovianTagsHandler. Manual additions are preserved.
    /// </summary>
    public static class JovianTagsGenerator {

        private const string DefaultClassName = "GameTags";
        private const string DefaultOutputPath = "Assets/GameTags.cs";
        private const string TagFieldPostfix = "_Tag";

        /// <summary>
        /// Collects all tag names from all GameTagSettings assets in the project.
        /// </summary>
        public static List<string> CollectAllTags() {
            var allTags = new HashSet<string>(StringComparer.Ordinal);
            var settings = JovianTagsEditorUtility.GetSettings();
            foreach(var s in settings) {
                foreach(var t in s.gameTags) {
                    if(!string.IsNullOrEmpty(t.tag)) {
                        allTags.Add(t.tag);
                    }
                }
            }
            return allTags.OrderBy(t => t, StringComparer.OrdinalIgnoreCase).ToList();
        }

        /// <summary>
        /// Generates the C# source content for the strong-typed tag fields.
        /// </summary>
        public static string GenerateFileContent(List<string> tagNames, string className) {
            var sb = new StringBuilder();

            // Header
            sb.AppendLine("//------------------------------------------------------------------------------");
            sb.AppendLine("//     Generated by Jovian Game Tag System");
            sb.AppendLine("//     Manual edits are preserved — fields added here will be kept on regeneration.");
            sb.AppendLine($"//     Last generated on {DateTime.Now:yyyy-MM-dd HH:mm}");
            sb.AppendLine("//------------------------------------------------------------------------------");
            sb.AppendLine();
            sb.AppendLine("using Jovian.TagSystem;");
            sb.AppendLine();
            sb.AppendLine($"public static partial class {className} {{");

            // Build tree structure from dot-delimited names
            var root = new TagNode("", "");
            foreach(var fullName in tagNames) {
                var parts = fullName.Split(JovianTagsHandler.tagDelimiter);
                var current = root;
                var accumulated = "";
                for(int i = 0; i < parts.Length; i++) {
                    var part = parts[i];
                    accumulated = i == 0 ? part : accumulated + "." + part;
                    if(!current.Children.TryGetValue(part, out var child)) {
                        child = new TagNode(part, accumulated);
                        current.Children[part] = child;
                    }
                    current = child;
                }
            }

            // Generate nested classes from tree
            GenerateNode(sb, root, 1);

            // Generate initializer methods
            var allNodes = FlattenNodes(root);

            sb.AppendLine();
            sb.AppendLine("    private static readonly string[] AllTags = {");
            for(int i = 0; i < allNodes.Count; i++) {
                var comma = i < allNodes.Count - 1 ? "," : "";
                sb.AppendLine($"        \"{allNodes[i].FullName}\"{comma}");
            }
            sb.AppendLine("    };");

            sb.AppendLine();
            sb.AppendLine("    [UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.BeforeSceneLoad)]");
            sb.AppendLine("    private static void RuntimeInitialize() {");
            sb.AppendLine("        JovianTagsHandler.Initialize();");
            sb.AppendLine("        JovianTagsHandler.RegisterTags(AllTags);");
            foreach(var node in allNodes) {
                sb.AppendLine($"        {node.FieldPath} = JovianTagsHandler.GetTag(\"{node.FullName}\");");
            }
            sb.AppendLine("    }");

            sb.AppendLine();
            sb.AppendLine("#if UNITY_EDITOR");
            sb.AppendLine("    [UnityEditor.InitializeOnLoadMethod]");
            sb.AppendLine("    private static void EditorInitialize() {");
            sb.AppendLine("        RuntimeInitialize();");
            sb.AppendLine("    }");
            sb.AppendLine("#endif");

            sb.AppendLine("}");
            return sb.ToString();
        }

        private static void GenerateNode(StringBuilder sb, TagNode node, int depth) {
            var indent = new string(' ', depth * 4);

            foreach(var child in node.Children.Values.OrderBy(c => c.Name, StringComparer.OrdinalIgnoreCase)) {
                var safeName = SanitizeIdentifier(child.Name);

                if(child.Children.Count > 0) {
                    // Node with children → nested class + field
                    sb.AppendLine();
                    sb.AppendLine($"{indent}public static class {safeName} {{");
                    sb.AppendLine($"{indent}    public static JovianTag {safeName}{TagFieldPostfix};");
                    GenerateNode(sb, child, depth + 1);
                    sb.AppendLine($"{indent}}}");
                }
                else {
                    // Leaf → just a field
                    sb.AppendLine($"{indent}public static JovianTag {safeName}{TagFieldPostfix};");
                }
            }
        }

        private static List<TagNode> FlattenNodes(TagNode root) {
            var result = new List<TagNode>();
            foreach(var child in root.Children.Values.OrderBy(c => c.FullName, StringComparer.OrdinalIgnoreCase)) {
                FlattenRecursive(child, result);
            }
            return result;
        }

        private static void FlattenRecursive(TagNode node, List<TagNode> result) {
            result.Add(node);
            foreach(var child in node.Children.Values.OrderBy(c => c.FullName, StringComparer.OrdinalIgnoreCase)) {
                FlattenRecursive(child, result);
            }
        }

        /// <summary>
        /// Writes the generated file and triggers AssetDatabase refresh.
        /// </summary>
        public static void WriteAndRefresh(string content, string outputPath) {
            outputPath = outputPath.Replace('\\', '/');
            var directory = Path.GetDirectoryName(outputPath);
            if(!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) {
                Directory.CreateDirectory(directory);
            }

            File.WriteAllText(outputPath, content);
            Debug.Log($"[Game Tag System] Generated tag file at: {outputPath}");
            AssetDatabase.Refresh();
        }

        /// <summary>
        /// Full regeneration from current GameTagSettings.
        /// </summary>
        public static void Regenerate(string outputPath = null) {
            if(string.IsNullOrEmpty(outputPath)) {
                outputPath = FindOrDefaultOutputPath();
            }
            var className = Path.GetFileNameWithoutExtension(outputPath);
            var tags = CollectAllTags();
            if(tags.Count == 0) {
                Debug.LogWarning("[Game Tag System] No tags found in any GameTagSettings asset.");
                return;
            }
            var content = GenerateFileContent(tags, className);
            WriteAndRefresh(content, outputPath);
        }

        /// <summary>
        /// Searches for an existing generated file or returns a default path.
        /// </summary>
        public static string FindOrDefaultOutputPath() {
            var guids = AssetDatabase.FindAssets($"{DefaultClassName} t:MonoScript");
            if(guids.Length > 0) {
                return AssetDatabase.GUIDToAssetPath(guids[0]);
            }
            return DefaultOutputPath;
        }

        [MenuItem("Jovian/Tag System/Regenerate Tag Constants")]
        private static void MenuRegenerate() {
            Regenerate();
        }

        private static readonly HashSet<string> CSharpKeywords = new(StringComparer.Ordinal) {
            "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char",
            "checked", "class", "const", "continue", "decimal", "default", "delegate", "do",
            "double", "else", "enum", "event", "explicit", "extern", "false", "finally",
            "fixed", "float", "for", "foreach", "goto", "if", "implicit", "in", "int",
            "interface", "internal", "is", "lock", "long", "namespace", "new", "null",
            "object", "operator", "out", "override", "params", "private", "protected",
            "public", "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof",
            "stackalloc", "static", "string", "struct", "switch", "this", "throw", "true",
            "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using",
            "virtual", "void", "volatile", "while"
        };

        private static readonly System.Text.RegularExpressions.Regex ValidIdentifierRegex =
            new(@"^[A-Za-z][A-Za-z0-9_]*$");

        /// <summary>
        /// Validates a single tag segment (one part between dots).
        /// Returns null if valid, or an error message if invalid.
        /// </summary>
        public static string ValidateTagSegment(string segment) {
            if(string.IsNullOrWhiteSpace(segment))
                return "Tag name cannot be empty.";

            if(!ValidIdentifierRegex.IsMatch(segment))
                return $"'{segment}' must start with a letter and contain only letters, digits, or underscores.";

            if(CSharpKeywords.Contains(segment))
                return $"'{segment}' is a C# reserved keyword.";

            return null;
        }

        /// <summary>
        /// Validates a full dot-delimited tag name. Checks each segment.
        /// Returns null if valid, or an error message for the first invalid segment.
        /// </summary>
        public static string ValidateTagName(string tagName) {
            if(string.IsNullOrWhiteSpace(tagName))
                return "Tag name cannot be empty.";

            var segments = tagName.Split(JovianTagsHandler.tagDelimiter);
            foreach(var segment in segments) {
                var error = ValidateTagSegment(segment);
                if(error != null) return error;
            }
            return null;
        }

        private static string SanitizeIdentifier(string name) {
            if(string.IsNullOrWhiteSpace(name)) return "_Empty";
            var sanitized = System.Text.RegularExpressions.Regex.Replace(name.Trim(), @"[^A-Za-z0-9_]", "_");
            if(char.IsDigit(sanitized[0])) sanitized = "_" + sanitized;
            if(CSharpKeywords.Contains(sanitized)) sanitized = "_" + sanitized;
            return sanitized;
        }

        private class TagNode {
            public string Name;
            public string FullName;
            public Dictionary<string, TagNode> Children = new(StringComparer.Ordinal);

            public TagNode(string name, string fullName) {
                Name = name;
                FullName = fullName;
            }

            /// <summary>
            /// Returns the fully qualified C# field path for this node's tag field.
            /// Nodes with children are nested classes, so the path includes the class hierarchy.
            /// Leaf nodes at root level are just field names.
            /// E.g., "Character.Player" → "Character.Player.Player_Tag"
            /// E.g., "big" (leaf, no children) → "big_Tag"
            /// </summary>
            public string FieldPath {
                get {
                    var parts = FullName.Split(JovianTagsHandler.tagDelimiter);
                    var safeParts = parts.Select(p =>
                        System.Text.RegularExpressions.Regex.Replace(p.Trim(), @"[^A-Za-z0-9_]", "_")).ToArray();
                    var lastSafe = safeParts[^1];

                    // Nodes with children become classes; their _Tag field lives inside the class.
                    // Leaf nodes are just fields inside their parent class.
                    //
                    // Examples (→ = generates):
                    //   "big" (root leaf)              → big_Tag
                    //   "Damage" (root with children)  → Damage.Damage_Tag
                    //   "Damage.Fire" (child leaf)     → Damage.Fire_Tag
                    //   "Damage.Fire.AOE" (deep leaf)  → Damage.Fire.AOE_Tag
                    //   "Damage.Fire" (with children)  → Damage.Fire.Fire_Tag

                    if(Children.Count > 0) {
                        // This node is a class — its _Tag field is inside itself
                        // Class path = all segments joined, field = lastSegment_Tag
                        return string.Join(".", safeParts) + "." + lastSafe + TagFieldPostfix;
                    }

                    if(safeParts.Length == 1) {
                        // Root leaf — field directly in outer class
                        return lastSafe + TagFieldPostfix;
                    }

                    // Non-root leaf — field inside parent class
                    // Parent class path = all segments except last
                    var parentPath = string.Join(".", safeParts.Take(safeParts.Length - 1));
                    return parentPath + "." + lastSafe + TagFieldPostfix;
                }
            }
        }
    }
}
