using System.Collections.Concurrent;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;

namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Templates;

/// <summary>
/// In-memory <see cref="ITemplateGraphWatermark"/>. Registered as a singleton so
/// the writer (the configuration-database repository's unit-of-work commit) and
/// the readers (the flatten-session cache, the staleness fast path) observe the
/// same counters within a process.
///
/// <para>
/// Lives in Commons rather than a component project because both the
/// ConfigurationDatabase writer and the DeploymentManager readers need it and
/// neither references the other. It is a pure in-memory primitive with no
/// infrastructure dependency, so it does not violate the "implementations live
/// with their component" convention the way a repository would.
/// </para>
///
/// <para>
/// All counters are <see cref="Interlocked"/>-updated and the per-id maps are
/// <see cref="ConcurrentDictionary{TKey,TValue}"/>, so concurrent management
/// commands (which each run on their own DI scope) can bump safely. Reads are
/// deliberately unsynchronised relative to each other: a reader that observes a
/// half-applied batch of bumps sees a CHANGED value for at least one member,
/// which invalidates rather than falsely validates a cache entry.
/// </para>
/// </summary>
public sealed class TemplateGraphWatermark : ITemplateGraphWatermark
{
    private readonly ConcurrentDictionary<int, long> _templateVersions = new();
    private readonly ConcurrentDictionary<int, long> _instanceVersions = new();
    private long _global;
    private long _structure;

    /// <inheritdoc />
    public long Global => Interlocked.Read(ref _global);

    /// <inheritdoc />
    public long StructureVersion => Interlocked.Read(ref _structure);

    /// <inheritdoc />
    public long GetTemplateVersion(int templateId) =>
        _templateVersions.TryGetValue(templateId, out var v) ? v : 0L;

    /// <inheritdoc />
    public long GetInstanceVersion(int instanceId) =>
        _instanceVersions.TryGetValue(instanceId, out var v) ? v : 0L;

    /// <inheritdoc />
    public void BumpTemplate(int templateId, bool structural = false)
    {
        _templateVersions.AddOrUpdate(templateId, 1L, static (_, current) => current + 1);
        Interlocked.Increment(ref _global);
        if (structural)
            Interlocked.Increment(ref _structure);
    }

    /// <inheritdoc />
    public void BumpInstance(int instanceId)
    {
        _instanceVersions.AddOrUpdate(instanceId, 1L, static (_, current) => current + 1);
        Interlocked.Increment(ref _global);
    }

    /// <inheritdoc />
    public void BumpAll()
    {
        Interlocked.Increment(ref _global);
        Interlocked.Increment(ref _structure);
    }
}
