using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;

namespace Jovian.TagSystem {

    /// <summary>
    /// Entry in a JovianTagContainer — pairs a tag with an optional typed value.
    /// </summary>
    [Serializable]
    public struct TagEntry<T> {
        public JovianTag Tag;
        public T Value;

        public TagEntry(JovianTag tag, T value) {
            Tag = tag;
            Value = value;
        }

        public TagEntry(JovianTag tag) {
            Tag = tag;
            Value = default;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public readonly bool Is(JovianTag other) => Tag.Equals(other);
        public readonly bool IsDescendantOf(JovianTag ancestor) => Tag.IsDescendantOf(ancestor);
        public readonly bool IsAncestorOf(JovianTag descendant) => Tag.IsAncestorOf(descendant);
        public readonly bool IsSiblingTo(JovianTag sibling) => Tag.IsSiblingTo(sibling);
        public readonly bool IsValid() => Tag.IsValid();

        public readonly override string ToString() => $"{Tag}: {Value}";
    }

    /// <summary>
    /// Generic tag container — holds tag+value pairs with hierarchy-aware queries.
    /// For tags-only, use non-generic JovianTagContainer.
    /// </summary>
    [Serializable]
    public class JovianTagsContainer<T> {
        public readonly List<TagEntry<T>> entries;

        public JovianTagsContainer(int capacity) {
            entries = new List<TagEntry<T>>(capacity);
        }

        public int Count => entries.Count;

        public void Add(JovianTag tag, T value) {
            for(int i = 0, n = entries.Count; i < n; i++) {
                if(entries[i].Tag.Id == tag.Id) return; // no duplicates
            }
            entries.Add(new TagEntry<T>(tag, value));
        }

        public bool Remove(JovianTag tag) {
            for(int i = entries.Count - 1; i >= 0; i--) {
                if(entries[i].Tag.Id == tag.Id) {
                    entries.RemoveAt(i);
                    return true;
                }
            }
            return false;
        }

        public void Clear() => entries.Clear();

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool Contains(JovianTag tag) {
            for(int i = 0, n = entries.Count; i < n; i++) {
                if(entries[i].Tag.Id == tag.Id) return true;
            }
            return false;
        }

        public bool TryGetValue(JovianTag tag, out T value) {
            for(int i = 0, n = entries.Count; i < n; i++) {
                if(entries[i].Tag.Id == tag.Id) {
                    value = entries[i].Value;
                    return true;
                }
            }
            value = default;
            return false;
        }

        public bool ContainsDescendantOf(JovianTag ancestor) {
            for(int i = 0, n = entries.Count; i < n; i++) {
                if(entries[i].Tag.IsDescendantOf(ancestor)) return true;
            }
            return false;
        }

        public bool ContainsAncestorOf(JovianTag descendant) {
            for(int i = 0, n = entries.Count; i < n; i++) {
                if(entries[i].Tag.IsAncestorOf(descendant)) return true;
            }
            return false;
        }

        public bool ContainsSibling(JovianTag sibling) {
            for(int i = 0, n = entries.Count; i < n; i++) {
                if(entries[i].Tag.IsSiblingTo(sibling)) return true;
            }
            return false;
        }

        public void GetByAncestor(JovianTag ancestor, List<TagEntry<T>> results) {
            for(int i = 0, n = entries.Count; i < n; i++) {
                if(entries[i].Tag.IsDescendantOf(ancestor)) results.Add(entries[i]);
            }
        }

        public void GetByDescendant(JovianTag descendant, List<TagEntry<T>> results) {
            for(int i = 0, n = entries.Count; i < n; i++) {
                if(entries[i].Tag.IsAncestorOf(descendant)) results.Add(entries[i]);
            }
        }

        public void GetBySibling(JovianTag sibling, List<TagEntry<T>> results) {
            for(int i = 0, n = entries.Count; i < n; i++) {
                if(entries[i].Tag.IsSiblingTo(sibling)) results.Add(entries[i]);
            }
        }

        private static readonly StringBuilder sb = new(256);

        public override string ToString() {
            sb.Clear();
            for(int i = 0, n = entries.Count; i < n; i++) {
                if(i > 0) sb.Append(" | ");
                sb.Append(entries[i].ToString());
            }
            return sb.ToString();
        }
    }

    /// <summary>
    /// Sentinel type for tag-only containers (no payload).
    /// </summary>
    public struct NoValue { }

    /// <summary>
    /// Non-generic tag container — tags only, no data payload.
    /// </summary>
    [Serializable]
    public class JovianTagsContainer : JovianTagsContainer<NoValue> {
        private static readonly JovianTagsContainer empty = new(0);
        public static JovianTagsContainer Empty => empty;

        public JovianTagsContainer(int capacity) : base(capacity) { }

        public void Add(JovianTag tag) => Add(tag, default);

        /// <summary>
        /// Access tags directly for backwards compatibility.
        /// </summary>
        public JovianTag this[int index] => entries[index].Tag;

        public JovianTag GetTag(int index) => entries[index].Tag;
    }
}
