namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns;

/// <summary>
/// The kind of node within the unified-namespace (UNS) browse tree. Determines
/// how the renderer styles the row and which entity (if any) it links to.
/// </summary>
public enum UnsNodeKind
{
    Enterprise,
    Cluster,
    Area,
    Line,
    Equipment,
    Tag,
    VirtualTag,
}

/// <summary>
/// View-model for a single node in the UNS browse tree. Carries the stable
/// identity, display text and lazy-load metadata the renderer needs, plus
/// transient UI state (expansion/loading) that is never persisted.
/// </summary>
public sealed class UnsNode
{
    /// <summary>The kind of node — drives styling and entity linking.</summary>
    public required UnsNodeKind Kind { get; init; }

    /// <summary>Stable per-node identifier, unique within the tree (e.g. <c>eq:{equipmentId}</c>).</summary>
    public required string Key { get; init; }

    /// <summary>Human-readable label shown in the tree.</summary>
    public required string DisplayName { get; init; }

    /// <summary>Owning cluster id for Area/Line/Equipment (and the cluster itself); null for Enterprise.</summary>
    public string? ClusterId { get; init; }

    /// <summary>This node's own logical entity id (UnsAreaId/UnsLineId/EquipmentId/TagId/VirtualTagId); null for Enterprise.</summary>
    public string? EntityId { get; init; }

    /// <summary>Badge count. For equipment this is tag + virtual-tag count; for container nodes it is the direct child count.</summary>
    public int ChildCount { get; set; }

    /// <summary>
    /// Gets a value indicating whether this node has children that load lazily (equipment with tags/virtual tags).
    /// Structural nodes (Cluster/Area/Line) always carry <c>false</c> even when they have eager children, so a
    /// renderer must decide whether to show an expand chevron with <c>Children.Count &gt; 0 || HasLazyChildren</c>,
    /// not on <c>HasLazyChildren</c> alone.
    /// </summary>
    public bool HasLazyChildren { get; init; }

    /// <summary>Eagerly-materialised children. Empty for lazy-loaded equipment until expanded.</summary>
    public List<UnsNode> Children { get; } = new();

    // --- Runtime UI state (not persisted) ---

    /// <summary>Whether the node is currently expanded in the UI.</summary>
    public bool Expanded { get; set; }

    /// <summary>Whether the node's lazy children have been loaded.</summary>
    public bool Loaded { get; set; }

    /// <summary>Whether a lazy-load is currently in flight for this node.</summary>
    public bool Loading { get; set; }

    /// <summary>Last load error message, if any.</summary>
    public string? Error { get; set; }
}

/// <summary>Flat structural row describing a cluster and its enterprise/site placement.</summary>
public readonly record struct ClusterRow(string ClusterId, string Enterprise, string Site, string Name);

/// <summary>Flat structural row describing a UNS area and its owning cluster.</summary>
public readonly record struct AreaRow(string UnsAreaId, string ClusterId, string Name);

/// <summary>Flat structural row describing a UNS line and its owning area.</summary>
public readonly record struct LineRow(string UnsLineId, string UnsAreaId, string Name);

/// <summary>Flat structural row describing an equipment node, its owning line and its tag/virtual-tag counts.</summary>
public readonly record struct EquipmentRow(
    string EquipmentId,
    string UnsLineId,
    string MachineCode,
    string Name,
    int TagCount,
    int VirtualTagCount);

/// <summary>
/// Pure, EF-free assembly of the UNS browse tree from flat structural rows.
/// Builds Enterprise → Cluster → Area → Line → Equipment with deterministic
/// ordinal ordering and equipment lazy-load metadata.
/// </summary>
public static class UnsTreeAssembly
{
    /// <summary>
    /// Builds the Enterprise→Cluster→Area→Line→Equipment tree. Empty clusters
    /// (and enterprises whose clusters have no areas) are retained. Ordering is
    /// deterministic and ordinal at every level.
    /// </summary>
    /// <param name="clusters">The flat set of cluster rows to nest under the enterprise root.</param>
    /// <param name="areas">The flat set of area rows to nest under their owning clusters.</param>
    /// <param name="lines">The flat set of line rows to nest under their owning areas.</param>
    /// <param name="equipment">The flat set of equipment rows to nest under their owning lines.</param>
    /// <returns>The assembled read-only list of top-level UNS tree nodes.</returns>
    public static IReadOnlyList<UnsNode> Build(
        IReadOnlyList<ClusterRow> clusters,
        IReadOnlyList<AreaRow> areas,
        IReadOnlyList<LineRow> lines,
        IReadOnlyList<EquipmentRow> equipment)
    {
        // Index children by their parent key for O(1) lookup during nesting.
        var areasByCluster = areas
            .GroupBy(a => a.ClusterId, StringComparer.Ordinal)
            .ToDictionary(g => g.Key, g => g.ToList(), StringComparer.Ordinal);

        var linesByArea = lines
            .GroupBy(l => l.UnsAreaId, StringComparer.Ordinal)
            .ToDictionary(g => g.Key, g => g.ToList(), StringComparer.Ordinal);

        var equipmentByLine = equipment
            .GroupBy(e => e.UnsLineId, StringComparer.Ordinal)
            .ToDictionary(g => g.Key, g => g.ToList(), StringComparer.Ordinal);

        // Top level: distinct enterprises, ordered ordinally.
        var enterprises = clusters
            .GroupBy(c => c.Enterprise, StringComparer.Ordinal)
            .OrderBy(g => g.Key, StringComparer.Ordinal)
            .Select(entGroup =>
            {
                var clusterNodes = entGroup
                    .OrderBy(c => c.Name, StringComparer.Ordinal)
                    .ThenBy(c => c.ClusterId, StringComparer.Ordinal)
                    .Select(c => BuildCluster(c, areasByCluster, linesByArea, equipmentByLine))
                    .ToList();

                var ent = new UnsNode
                {
                    Kind = UnsNodeKind.Enterprise,
                    Key = $"ent:{entGroup.Key}",
                    DisplayName = entGroup.Key,
                    ClusterId = null,
                    EntityId = null,
                    HasLazyChildren = false,
                };
                ent.Children.AddRange(clusterNodes);
                ent.ChildCount = ent.Children.Count;
                return ent;
            })
            .ToList();

        return enterprises;
    }

    private static UnsNode BuildCluster(
        ClusterRow cluster,
        IReadOnlyDictionary<string, List<AreaRow>> areasByCluster,
        IReadOnlyDictionary<string, List<LineRow>> linesByArea,
        IReadOnlyDictionary<string, List<EquipmentRow>> equipmentByLine)
    {
        var areaNodes = (areasByCluster.TryGetValue(cluster.ClusterId, out var clusterAreas)
                ? clusterAreas
                : Enumerable.Empty<AreaRow>())
            .OrderBy(a => a.Name, StringComparer.Ordinal)
            .ThenBy(a => a.UnsAreaId, StringComparer.Ordinal)
            .Select(a => BuildArea(a, cluster.ClusterId, linesByArea, equipmentByLine))
            .ToList();

        var node = new UnsNode
        {
            Kind = UnsNodeKind.Cluster,
            Key = $"clu:{cluster.ClusterId}",
            DisplayName = string.IsNullOrEmpty(cluster.Site)
                ? cluster.Name
                : $"{cluster.Site} ({cluster.Name})",
            ClusterId = cluster.ClusterId,
            EntityId = cluster.ClusterId, // Cluster's own logical id IS its ClusterId — EntityId mirrors it for uniform navigation.
            HasLazyChildren = false,
        };
        node.Children.AddRange(areaNodes);
        node.ChildCount = node.Children.Count;
        return node;
    }

    private static UnsNode BuildArea(
        AreaRow area,
        string clusterId,
        IReadOnlyDictionary<string, List<LineRow>> linesByArea,
        IReadOnlyDictionary<string, List<EquipmentRow>> equipmentByLine)
    {
        var lineNodes = (linesByArea.TryGetValue(area.UnsAreaId, out var areaLines)
                ? areaLines
                : Enumerable.Empty<LineRow>())
            .OrderBy(l => l.Name, StringComparer.Ordinal)
            .ThenBy(l => l.UnsLineId, StringComparer.Ordinal)
            .Select(l => BuildLine(l, clusterId, equipmentByLine))
            .ToList();

        var node = new UnsNode
        {
            Kind = UnsNodeKind.Area,
            Key = $"area:{area.UnsAreaId}",
            DisplayName = area.Name,
            ClusterId = clusterId,
            EntityId = area.UnsAreaId,
            HasLazyChildren = false,
        };
        node.Children.AddRange(lineNodes);
        node.ChildCount = node.Children.Count;
        return node;
    }

    private static UnsNode BuildLine(
        LineRow line,
        string clusterId,
        IReadOnlyDictionary<string, List<EquipmentRow>> equipmentByLine)
    {
        var equipmentNodes = (equipmentByLine.TryGetValue(line.UnsLineId, out var lineEquipment)
                ? lineEquipment
                : Enumerable.Empty<EquipmentRow>())
            .OrderBy(e => e.Name, StringComparer.Ordinal)
            .ThenBy(e => e.EquipmentId, StringComparer.Ordinal)
            .Select(e => BuildEquipment(e, clusterId))
            .ToList();

        var node = new UnsNode
        {
            Kind = UnsNodeKind.Line,
            Key = $"line:{line.UnsLineId}",
            DisplayName = line.Name,
            ClusterId = clusterId,
            EntityId = line.UnsLineId,
            HasLazyChildren = false,
        };
        node.Children.AddRange(equipmentNodes);
        node.ChildCount = node.Children.Count;
        return node;
    }

    private static UnsNode BuildEquipment(EquipmentRow equipment, string clusterId)
    {
        var childCount = equipment.TagCount + equipment.VirtualTagCount;
        return new UnsNode
        {
            Kind = UnsNodeKind.Equipment,
            Key = $"eq:{equipment.EquipmentId}",
            DisplayName = equipment.Name,
            ClusterId = clusterId,
            EntityId = equipment.EquipmentId,
            ChildCount = childCount,
            HasLazyChildren = false,
        };
    }
}
