using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

namespace ZB.MOM.WW.OtOpcUa.Commons.Browsing;

/// <summary>
///     In-memory <see cref="IAddressSpaceBuilder"/> that records the streamed discovery tree
///     for the universal browser instead of materializing OPC UA nodes. Single-threaded like
///     every builder a driver streams into during DiscoverAsync. Enforces a node cap: once
///     exceeded it stops recording (drivers keep streaming harmlessly) and marks the tree
///     truncated. See docs/plans/2026-07-15-universal-discovery-browser-design.md §4.1.
/// </summary>
public sealed class CapturingAddressSpaceBuilder : IAddressSpaceBuilder
{
    private sealed class CaptureState(int nodeCap)
    {
        public int Count;
        public bool Truncated;
        public readonly int NodeCap = nodeCap;
        public readonly Dictionary<string, CapturedNode> ById = new(StringComparer.Ordinal);
        public bool TryReserve()
        {
            if (Count >= NodeCap) { Truncated = true; return false; }
            Count++;
            return true;
        }
    }

    private readonly CaptureState _state;
    private readonly CapturedNode _scope;

    /// <summary>Create a root capturing builder with the given node cap (default 50 000).</summary>
    /// <param name="nodeCap">Maximum number of nodes recorded before the tree is marked truncated.</param>
    public CapturingAddressSpaceBuilder(int nodeCap = 50_000)
    {
        _state = new CaptureState(nodeCap);
        _scope = new CapturedNode { Id = "", Name = "", DisplayName = "", IsLeaf = false };
    }

    private CapturingAddressSpaceBuilder(CaptureState state, CapturedNode scope)
    {
        _state = state;
        _scope = scope;
    }

    /// <inheritdoc />
    public IAddressSpaceBuilder Folder(string browseName, string displayName)
    {
        var node = new CapturedNode
        {
            Id = _scope.Id + "/" + browseName,
            Name = browseName,
            DisplayName = displayName,
            IsLeaf = false,
        };
        if (_state.TryReserve())
        {
            _scope.Children.Add(node);
            _state.ById.TryAdd(node.Id, node);
        }
        // Over cap: the child builder writes into a detached node — recording stops, streaming doesn't break.
        return new CapturingAddressSpaceBuilder(_state, node);
    }

    /// <inheritdoc />
    public IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo attributeInfo)
    {
        var node = new CapturedNode
        {
            Id = attributeInfo.FullName,
            Name = browseName,
            DisplayName = displayName,
            IsLeaf = true,
            Attribute = attributeInfo,
        };
        if (_state.TryReserve())
        {
            _scope.Children.Add(node);
            _state.ById.TryAdd(node.Id, node);
        }
        return new CapturedVariableHandle(node);
    }

    /// <inheritdoc />
    public void AddProperty(string browseName, DriverDataType dataType, object? value) =>
        _scope.Properties.Add((browseName, dataType, value));

    /// <summary>Snapshot the captured tree. Only valid on the root builder (the one the browser constructed).</summary>
    /// <returns>An immutable-after-build <see cref="CapturedTree"/>.</returns>
    public CapturedTree Build() => new()
    {
        Root = _scope,
        Truncated = _state.Truncated,
        Count = _state.Count,
        ById = _state.ById,
    };

    private sealed class CapturedVariableHandle(CapturedNode node) : IVariableHandle
    {
        public string FullReference => node.Id;
        public IAlarmConditionSink MarkAsAlarmCondition(AlarmConditionInfo info)
        {
            node.IsAlarmMarked = true;
            return NoopAlarmSink.Instance;
        }
    }

    private sealed class NoopAlarmSink : IAlarmConditionSink
    {
        public static readonly NoopAlarmSink Instance = new();
        public void OnTransition(AlarmEventArgs args) { } // browse never delivers live transitions
    }
}
