using System.Collections.Concurrent;
using Mbproxy.Bcd;

namespace Mbproxy.Proxy;

/// <summary>
/// Process-wide registry of per-PLC <see cref="TagValueCapture"/> instances — the
/// shared seam between the proxy hot path (which records tag values) and the admin
/// layer (which arms captures on detail-page open and reads their snapshots).
///
/// <para>Registered as a DI singleton. <see cref="ProxyWorker"/> and
/// <see cref="Configuration.ConfigReconciler"/> call <see cref="GetOrCreate"/> as they
/// build each <see cref="PerPlcContext"/>; <see cref="Configuration.ConfigReconciler"/>
/// calls <see cref="Remove"/> for hot-reload-removed PLCs. The admin
/// <c>StatusHub</c> / <c>StatusBroadcaster</c> call <see cref="Arm"/> /
/// <see cref="Disarm"/> / <see cref="DisarmAll"/> / <see cref="TryGet"/>.</para>
/// </summary>
internal sealed class TagCaptureRegistry
{
    private readonly ConcurrentDictionary<string, TagValueCapture> _captures =
        new(StringComparer.Ordinal);

    /// <summary>
    /// Returns the capture for <paramref name="plcName"/>, creating it on first call.
    /// A subsequent call (hot-reload reseat/restart, where the tag set may have changed)
    /// rebuilds the capture for <paramref name="map"/>'s current tags, preserving the
    /// armed flag so an open detail page keeps capturing across the reload.
    /// </summary>
    public TagValueCapture GetOrCreate(string plcName, BcdTagMap map)
        => _captures.AddOrUpdate(
            plcName,
            _ => new TagValueCapture(map.All),
            (_, existing) =>
            {
                var rebuilt = new TagValueCapture(map.All);
                if (existing.IsArmed)
                    rebuilt.Arm();
                return rebuilt;
            });

    /// <summary>Drops the capture for a hot-reload-removed PLC.</summary>
    public void Remove(string plcName) => _captures.TryRemove(plcName, out _);

    /// <summary>Looks up a PLC's capture; false when the PLC is unknown.</summary>
    public bool TryGet(string plcName, out TagValueCapture capture)
        => _captures.TryGetValue(plcName, out capture!);

    /// <summary>Arms a PLC's capture. No-op for an unknown PLC name.</summary>
    public void Arm(string plcName)
    {
        if (_captures.TryGetValue(plcName, out var c))
            c.Arm();
    }

    /// <summary>Disarms a PLC's capture. No-op for an unknown PLC name.</summary>
    public void Disarm(string plcName)
    {
        if (_captures.TryGetValue(plcName, out var c))
            c.Disarm();
    }

    /// <summary>
    /// Disarms every capture. Called when the admin endpoint stops (e.g. AdminPort
    /// hot-reload tears down the SignalR host) so no capture is left armed with no viewer.
    /// </summary>
    public void DisarmAll()
    {
        foreach (var c in _captures.Values)
            c.Disarm();
    }
}
