namespace ScadaLink.Commons.Types;

/// <summary>
/// Monitors a heartbeat tag subscription for staleness. If no value is received
/// within <see cref="MaxSilence"/>, the <see cref="Stale"/> event fires.
/// Composable into any IDataConnection adapter.
/// </summary>
public sealed class StaleTagMonitor : IDisposable
{
    private readonly TimeSpan _maxSilence;
    private Timer? _timer;
    private volatile bool _staleFired;

    public StaleTagMonitor(TimeSpan maxSilence)
    {
        if (maxSilence <= TimeSpan.Zero)
            throw new ArgumentOutOfRangeException(nameof(maxSilence), "MaxSilence must be positive.");
        _maxSilence = maxSilence;
    }

    /// <summary>
    /// Fires when no value has been received within <see cref="MaxSilence"/>.
    /// Fires once per stale period — resets after <see cref="OnValueReceived"/> is called.
    /// </summary>
    public event Action? Stale;

    public TimeSpan MaxSilence => _maxSilence;

    /// <summary>
    /// Start monitoring. The timer begins counting from now.
    /// </summary>
    public void Start()
    {
        _staleFired = false;
        _timer?.Dispose();
        _timer = new Timer(OnTimerElapsed, null, _maxSilence, Timeout.InfiniteTimeSpan);
    }

    /// <summary>
    /// Signal that a value was received. Resets the stale timer.
    /// </summary>
    public void OnValueReceived()
    {
        _staleFired = false;
        _timer?.Change(_maxSilence, Timeout.InfiniteTimeSpan);
    }

    /// <summary>
    /// Stop monitoring and dispose the timer.
    /// </summary>
    public void Stop()
    {
        _timer?.Dispose();
        _timer = null;
    }

    public void Dispose()
    {
        Stop();
    }

    private void OnTimerElapsed(object? state)
    {
        if (_staleFired) return;
        _staleFired = true;
        Stale?.Invoke();
    }
}
