using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
using UnityEngine;

namespace Jovian.TagSystem {

    [Serializable]
    public struct RegisteredTag : IEquatable<RegisteredTag> {
        public string tag;

        public RegisteredTag(string tag) {
            this.tag = tag;
        }

        public bool Equals(RegisteredTag other) => tag == other.tag;
        public override bool Equals([CanBeNull] object obj) => obj is RegisteredTag other && Equals(other);
        public override int GetHashCode() => tag != null ? tag.GetHashCode() : 0;
    }

    public static class JovianTagsHandler {
        public const char tagDelimiter = '.';
        public const string emptyTagName = "None";
        public const int emptyTagId = 0;
        public static readonly JovianTag emptyTag = new(emptyTagId, 0);

        // Primary lookups — no allocations on query
        private static Dictionary<string, JovianTag> tagsByName = new();
        private static Dictionary<int, JovianTag> tagsById = new();
        private static Dictionary<int, string> tagNames = new();
        private static int idCounter;
        private static bool initialized;

        public static bool IsInitialized => initialized;

        public static void Initialize() {
            tagsByName = new Dictionary<string, JovianTag>(64) { { emptyTagName, emptyTag } };
            tagsById = new Dictionary<int, JovianTag>(64) { { emptyTagId, emptyTag } };
            tagNames = new Dictionary<int, string>(64) { { emptyTagId, emptyTagName } };
            idCounter = 0;
            initialized = true;
        }

        public static void EnsureInitialized() {
            if(!initialized) Initialize();
        }

        public static void Reset() {
            tagsByName = new Dictionary<string, JovianTag>();
            tagsById = new Dictionary<int, JovianTag>();
            tagNames = new Dictionary<int, string>();
            idCounter = 0;
            initialized = false;
        }

        public static void RegisterTags(RegisteredTag[] serializedTags) {
            EnsureInitialized();
            for(int i = 0, n = serializedTags.Length; i < n; i++) {
                RegisterTag(serializedTags[i].tag);
            }
        }

        public static void RegisterTags(string[] tagNames) {
            EnsureInitialized();
            for(int i = 0, n = tagNames.Length; i < n; i++) {
                RegisterTag(tagNames[i]);
            }
        }

        public static void RegisterTag(string tagToRegister) {
            EnsureInitialized();

            // Walk segments without allocating a string[] — use ReadOnlySpan
            var span = tagToRegister.AsSpan();
            int parentId = 0;

            for(int i = 0; i <= span.Length; i++) {
                if(i < span.Length && span[i] != tagDelimiter) {
                    continue;
                }

                // span[segStart..i] is the current segment
                // Full tag name is tagToRegister[0..i]
                var fullName = tagToRegister.Substring(0, i);

                if(!tagsByName.TryGetValue(fullName, out var tag)) {
                    idCounter++;
                    tag = new JovianTag(idCounter, parentId);
                    tagsByName[fullName] = tag;
                    tagsById[idCounter] = tag;
                    tagNames[idCounter] = fullName;
                }

                parentId = tag.Id;
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static JovianTag GetTag(string tagName) {
            if(string.IsNullOrEmpty(tagName)) {
                return emptyTag;
            }
            if(tagsByName.TryGetValue(tagName, out var tag)) {
                return tag;
            }
            Debug.LogError($"[TagManager] Trying to get unregistered Tag: {tagName}");
            return emptyTag;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static JovianTag GetTag(int id) {
            return tagsById.GetValueOrDefault(id, emptyTag);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool TryGetGameTag(string tagName, out JovianTag tag) {
            tag = emptyTag;
            return !string.IsNullOrEmpty(tagName) && tagsByName.TryGetValue(tagName, out tag);
        }

        public static bool TryGetGameTagThatIsNotNone(string tagName, out JovianTag tag) {
            if(!TryGetGameTag(tagName, out tag)) {
                return false;
            }
            return tag.Id != emptyTagId;
        }

        public static void GetAllTags(List<JovianTag> results) {
            foreach(var kvp in tagsByName) {
                if(kvp.Value.Id != emptyTagId)
                    results.Add(kvp.Value);
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static string TagToString(JovianTag jovianTag) {
            return tagNames.TryGetValue(jovianTag.Id, out var text) ? text : string.Empty;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static string TagToString(int tagId) {
            return tagNames.TryGetValue(tagId, out var text) ? text : string.Empty;
        }

        public static string DisplayName(string name) {
            var lastDot = name.LastIndexOf(tagDelimiter);
            return lastDot < 0 ? name : name.Substring(lastDot + 1);
        }
    }
}
