//! RFC-0020 & RFC-0120: Deterministic Frame Sequencer
//!
//! Ensures that frames are presented to L1 in a stable, deterministic order
//! regardless of network arrival order. This is critical for state machine
//! consistency and preventing race conditions in the Reality Tunnel.

const std = @import("std");
const manifest = @import("manifest.zig");

/// Sorts a slice of PacketSummaries deterministically.
/// Ordering Doctrine:
/// 1. Primary: Source Hint (truncated DID) - groups frames by sender
/// 2. Secondary: Timestamp (L0 nanoseconds) - causal ordering
/// 3. Tertiary: Sequence (Tie-breaker for same-nanosecond frames)
/// 4. Quaternary: QueueID (Force stability for identical metadata)
pub fn sortDeterministically(items: []manifest.PacketSummary) void {
    std.sort.pdq(manifest.PacketSummary, items, {}, comparePackets);
}

fn comparePackets(_: void, a: manifest.PacketSummary, b: manifest.PacketSummary) bool {
    // 1. Source Hint
    const hint_cmp = std.mem.order(u8, &a.sender_hint, &b.sender_hint);
    if (hint_cmp != .eq) return hint_cmp == .lt;

    // 2. Timestamp
    if (a.timestamp != b.timestamp) return a.timestamp < b.timestamp;

    // 3. Sequence
    if (a.sequence != b.sequence) return a.sequence < b.sequence;

    // 4. Queue ID (Total Stability)
    return std.mem.order(u8, &a.queue_id, &b.queue_id) == .lt;
}

pub const ReplayFilter = struct {
    last_sequences: std.AutoHashMapUnmanaged([24]u8, u32),
    allocator: std.mem.Allocator,

    pub fn init(allocator: std.mem.Allocator) ReplayFilter {
        return .{
            .last_sequences = .{},
            .allocator = allocator,
        };
    }

    pub fn deinit(self: *ReplayFilter) void {
        self.last_sequences.deinit(self.allocator);
    }

    /// Checks if a packet is a replay.
    /// Returns true if the packet should be processed (new sequence),
    /// false if it should be dropped (replay or old).
    pub fn isNew(self: *ReplayFilter, sender: [24]u8, sequence: u32) !bool {
        const entry = try self.last_sequences.getOrPut(self.allocator, sender);
        if (!entry.found_existing) {
            entry.value_ptr.* = sequence;
            return true;
        }

        if (sequence > entry.value_ptr.*) {
            entry.value_ptr.* = sequence;
            return true;
        }

        return false; // Replay or old packet
    }
};

test "Deterministic Sorting" {
    var summaries = [_]manifest.PacketSummary{
        .{
            .queue_id = [_]u8{3} ** 16,
            .sender_hint = [_]u8{0xA} ** 24,
            .size = 100,
            .priority = .normal,
            .created_at = 0,
            .timestamp = 200,
            .sequence = 2,
            .expires_at = 0,
            .entropy_cost = 0,
            .category = .peer,
        },
        .{
            .queue_id = [_]u8{1} ** 16,
            .sender_hint = [_]u8{0xA} ** 24,
            .size = 100,
            .priority = .normal,
            .created_at = 0,
            .timestamp = 100,
            .sequence = 1,
            .expires_at = 0,
            .entropy_cost = 0,
            .category = .peer,
        },
        .{
            .queue_id = [_]u8{2} ** 16,
            .sender_hint = [_]u8{0xB} ** 24,
            .size = 100,
            .priority = .normal,
            .created_at = 0,
            .timestamp = 50,
            .sequence = 5,
            .expires_at = 0,
            .entropy_cost = 0,
            .category = .peer,
        },
    };

    sortDeterministically(&summaries);

    // Source A comes before Source B
    // Within Source A, timestamp 100 comes before 200
    try std.testing.expectEqual(summaries[0].sender_hint[0], 0xA);
    try std.testing.expectEqual(summaries[0].timestamp, 100);
    try std.testing.expectEqual(summaries[1].timestamp, 200);
    try std.testing.expectEqual(summaries[2].sender_hint[0], 0xB);
}

test "Replay Filter" {
    const allocator = std.testing.allocator;
    var filter = ReplayFilter.init(allocator);
    defer filter.deinit();

    const sender = [_]u8{0xC} ** 24;

    try std.testing.expect(try filter.isNew(sender, 10));
    try std.testing.expect(try filter.isNew(sender, 11));
    try std.testing.expect(!(try filter.isNew(sender, 10))); // Replay
    try std.testing.expect(!(try filter.isNew(sender, 5))); // Older
}
