using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;

namespace ZB.MOM.WW.MxGateway.Server.Galaxy;

public sealed class GalaxyHierarchyIndex
{
    private GalaxyHierarchyIndex(
        IReadOnlyList<GalaxyObjectView> objectViews,
        IReadOnlyDictionary<int, GalaxyObjectView> objectViewsById,
        IReadOnlyDictionary<string, GalaxyTagLookup> tagsByAddress)
    {
        ObjectViews = objectViews;
        ObjectViewsById = objectViewsById;
        TagsByAddress = tagsByAddress;
    }

    public static GalaxyHierarchyIndex Empty { get; } = new(
        Array.Empty<GalaxyObjectView>(),
        new Dictionary<int, GalaxyObjectView>(),
        new Dictionary<string, GalaxyTagLookup>(StringComparer.OrdinalIgnoreCase));

    public IReadOnlyList<GalaxyObjectView> ObjectViews { get; }

    public IReadOnlyDictionary<int, GalaxyObjectView> ObjectViewsById { get; }

    public IReadOnlyDictionary<string, GalaxyTagLookup> TagsByAddress { get; }

    public static GalaxyHierarchyIndex Build(IReadOnlyList<GalaxyObject> objects)
    {
        if (objects.Count == 0)
        {
            return Empty;
        }

        Dictionary<int, GalaxyObject> objectsById = new();
        foreach (GalaxyObject obj in objects)
        {
            objectsById.TryAdd(obj.GobjectId, obj);
        }

        List<GalaxyObjectView> views = new(objects.Count);
        Dictionary<int, GalaxyObjectView> viewsById = new();
        Dictionary<string, GalaxyTagLookup> tagsByAddress = new(StringComparer.OrdinalIgnoreCase);

        foreach (GalaxyObject obj in objects)
        {
            string path = BuildContainedPath(obj, objectsById);
            int depth = string.IsNullOrWhiteSpace(path) ? 0 : path.Count(character => character == '/');
            GalaxyObjectView view = new(obj, path, depth);
            views.Add(view);
            viewsById.TryAdd(obj.GobjectId, view);

            if (!string.IsNullOrWhiteSpace(obj.TagName))
            {
                tagsByAddress.TryAdd(obj.TagName, new GalaxyTagLookup(obj, Attribute: null, path));
            }

            foreach (GalaxyAttribute attribute in obj.Attributes)
            {
                if (!string.IsNullOrWhiteSpace(attribute.FullTagReference))
                {
                    tagsByAddress.TryAdd(attribute.FullTagReference, new GalaxyTagLookup(obj, attribute, path));
                }
            }
        }

        return new GalaxyHierarchyIndex(
            views,
            viewsById,
            tagsByAddress);
    }

    private static string BuildContainedPath(
        GalaxyObject obj,
        IReadOnlyDictionary<int, GalaxyObject> objectsById)
    {
        Stack<string> names = new();
        HashSet<int> seen = [];
        GalaxyObject? current = obj;
        while (current is not null && seen.Add(current.GobjectId))
        {
            names.Push(ResolvePathSegment(current));
            current = current.ParentGobjectId != 0
                && objectsById.TryGetValue(current.ParentGobjectId, out GalaxyObject? parent)
                    ? parent
                    : null;
        }

        return string.Join('/', names.Where(name => !string.IsNullOrWhiteSpace(name)));
    }

    private static string ResolvePathSegment(GalaxyObject obj)
    {
        if (!string.IsNullOrWhiteSpace(obj.ContainedName))
        {
            return obj.ContainedName;
        }

        if (!string.IsNullOrWhiteSpace(obj.BrowseName))
        {
            return obj.BrowseName;
        }

        return obj.TagName;
    }
}
