using ZB.MOM.WW.OtOpcUa.Runtime.Drivers;

namespace ZB.MOM.WW.OtOpcUa.Runtime.Redundancy;

/// <summary>
///     A singleton snapshot of the alarm-history drain decision, so code that lives outside an actor
///     can ask "should this node be draining the alarm queue right now?".
/// </summary>
/// <remarks>
///     <para>
///         Exists for the alarm store-and-forward drain. That drain runs on a timer owned by the
///         sink, not on an actor mailbox, so it cannot receive <c>RedundancyStateChanged</c>
///         directly — but it must be role-scoped, because the queue it drains replicates to the
///         pair peer and two draining nodes would deliver every event twice.
///     </para>
///     <para>
///         Deliberately publishes the <i>decision</i> rather than the role that produced it, so
///         <see cref="PrimaryGatePolicy"/> stays the single place that decides.
///     </para>
///     <para>
///         <b>This is not the device-write gate.</b> It is fed by
///         <see cref="PrimaryGatePolicy.ShouldDrainAlarmHistory"/>, which opens on an unknown role
///         where <see cref="PrimaryGatePolicy.ShouldServiceAsPrimary"/> closes. The naming is
///         explicit about the consumer for that reason: an earlier revision published the
///         write-gate verdict here, and on a cluster with several driver nodes but no configured
///         redundancy it suspended the drain everywhere, so alarm history accumulated on every node
///         and left none.
///     </para>
/// </remarks>
public interface IRedundancyRoleView
{
    /// <summary>Whether this node should be draining the alarm store-and-forward queue right now.</summary>
    bool ShouldDrainAlarmHistory { get; }

    /// <summary>Records a fresh decision. Called by <c>DriverHostActor</c> on every redundancy snapshot.</summary>
    /// <param name="shouldDrainAlarmHistory">
    ///     The decision <see cref="PrimaryGatePolicy.ShouldDrainAlarmHistory"/> produced.
    /// </param>
    void Publish(bool shouldDrainAlarmHistory);
}

/// <summary>Thread-safe <see cref="IRedundancyRoleView"/> backed by a volatile field.</summary>
public sealed class RedundancyRoleView : IRedundancyRoleView
{
    // Seeded with the decision for "role unknown" rather than a bare false. An unpublished view and a
    // node whose role nothing ever reports are genuinely the same situation, and they must behave the
    // same: a deployment that runs no redundancy at all never publishes here, and defaulting closed
    // would silently stop its alarm history forever.
    private volatile bool _shouldDrainAlarmHistory =
        PrimaryGatePolicy.ShouldDrainAlarmHistory(localRole: null, queueSharingPeerIsPrimary: false);

    /// <inheritdoc/>
    public bool ShouldDrainAlarmHistory => _shouldDrainAlarmHistory;

    /// <inheritdoc/>
    public void Publish(bool shouldDrainAlarmHistory) => _shouldDrainAlarmHistory = shouldDrainAlarmHistory;
}
