// Go: consumer.go isFilteredMatch, skipMsgs tracking (~line 4200)
namespace NATS.Server.JetStream.Consumers;

using NATS.Server.Subscriptions;

/// <summary>
/// Tracks filter matching for consumer message delivery.
/// Uses SubjectMatch.MatchLiteral() for NATS token-based filter matching (not Regex).
/// Maintains a sorted set of skipped sequences for gap tracking.
/// Go reference: consumer.go isFilteredMatch, skipMsgs tracking.
/// </summary>
public sealed class FilterSkipTracker
{
    private readonly string? _filterSubject;
    private readonly IReadOnlyList<string> _filterSubjects;
    private readonly SortedSet<ulong> _skippedSequences = new();
    private long _matchCount;
    private long _skipCount;

    public FilterSkipTracker(string? filterSubject = null, IReadOnlyList<string>? filterSubjects = null)
    {
        _filterSubject = filterSubject;
        _filterSubjects = filterSubjects ?? Array.Empty<string>();
    }

    /// <summary>Number of messages that matched the filter.</summary>
    public long MatchCount => Interlocked.Read(ref _matchCount);

    /// <summary>Number of messages that were skipped (didn't match filter).</summary>
    public long SkipCount => Interlocked.Read(ref _skipCount);

    /// <summary>Number of currently tracked skipped sequences.</summary>
    public int SkippedSequenceCount => _skippedSequences.Count;

    /// <summary>
    /// Returns true if the filter is active (has at least one filter subject).
    /// </summary>
    public bool HasFilter => !string.IsNullOrEmpty(_filterSubject) || _filterSubjects.Count > 0;

    /// <summary>
    /// Checks if a message subject matches the filter.
    /// Returns true (should deliver) when:
    /// - No filter is configured
    /// - Subject matches the single filter
    /// - Subject matches any of the multiple filters
    /// Uses SubjectMatch.MatchLiteral for NATS token-based matching.
    /// Go reference: consumer.go isFilteredMatch.
    /// </summary>
    public bool ShouldDeliver(string subject)
    {
        if (!HasFilter)
        {
            Interlocked.Increment(ref _matchCount);
            return true;
        }

        if (!string.IsNullOrEmpty(_filterSubject))
        {
            if (SubjectMatch.MatchLiteral(subject, _filterSubject))
            {
                Interlocked.Increment(ref _matchCount);
                return true;
            }
        }

        foreach (var filter in _filterSubjects)
        {
            if (SubjectMatch.MatchLiteral(subject, filter))
            {
                Interlocked.Increment(ref _matchCount);
                return true;
            }
        }

        Interlocked.Increment(ref _skipCount);
        return false;
    }

    /// <summary>
    /// Records a skipped sequence for gap tracking.
    /// </summary>
    public void RecordSkip(ulong sequence)
    {
        _skippedSequences.Add(sequence);
    }

    /// <summary>
    /// Returns the next unskipped sequence >= startSeq.
    /// Used to find the next deliverable message efficiently.
    /// </summary>
    public ulong NextUnskippedSequence(ulong startSeq)
    {
        var seq = startSeq;
        while (_skippedSequences.Contains(seq))
            seq++;
        return seq;
    }

    /// <summary>
    /// Clears skipped sequences below the given floor (e.g., ack floor).
    /// Prevents unbounded growth.
    /// </summary>
    public void PurgeBelow(ulong floor)
    {
        _skippedSequences.RemoveWhere(s => s < floor);
    }

    /// <summary>Resets all state.</summary>
    public void Reset()
    {
        _skippedSequences.Clear();
        Interlocked.Exchange(ref _matchCount, 0);
        Interlocked.Exchange(ref _skipCount, 0);
    }
}
