using ZB.MOM.WW.Secrets.Abstractions;

namespace ZB.MOM.WW.Secrets.Replicator.AkkaDotNet.DependencyInjection;

/// <summary>
/// Resolves the real <see cref="ISecretCacheInvalidator"/> on first eviction instead of at
/// construction, so the replication graph can be wired without closing a dependency cycle.
/// </summary>
/// <remarks>
/// <para>
/// This deferral is load-bearing, not an optimization (scadaproj#1). The invalidator is the
/// resolver, the resolver reads through <c>ISecretStore</c>, and with replication registered
/// <c>ISecretStore</c> is the <c>ReplicatingSecretStore</c> decorator whose replicator owns this
/// node's actor — so resolving the invalidator eagerly inside the actor provider's factory closes
/// a singleton cycle the container cannot see through factory lambdas. Resolution then recurses
/// until MS.DI's <c>StackGuard</c> hops it onto another thread, which deadlocks on a call-site
/// lock the first thread holds: any host resolving the graph hangs at startup, silently and
/// permanently.
/// </para>
/// <para>
/// Deferring to first eviction is sufficient because evictions only happen when a replicated row
/// is applied — strictly after the graph has finished resolving, at which point every singleton on
/// the former cycle is already materialized and the lookup completes without re-entering the
/// container's resolution locks.
/// </para>
/// </remarks>
/// <param name="resolve">
/// Lookup for the real invalidator; returns <c>null</c> when the application registered none.
/// </param>
internal sealed class DeferredSecretCacheInvalidator(Func<ISecretCacheInvalidator?> resolve)
    : ISecretCacheInvalidator
{
    private readonly Lazy<ISecretCacheInvalidator?> _inner = new(
        resolve ?? throw new ArgumentNullException(nameof(resolve)),
        LazyThreadSafetyMode.ExecutionAndPublication);

    /// <inheritdoc />
    public void Invalidate(SecretName name) => _inner.Value?.Invalidate(name);
}
