//! QVL Core Types for Advanced Graph Algorithms
//!
//! Extends RFC-0120 TrustEdge with risk scoring for Bellman-Ford.

const std = @import("std");
const time = @import("time");

const SovereignTimestamp = time.SovereignTimestamp;

/// Node identifier (compact u32 index into DID storage)
pub const NodeId = u32;

/// Extended edge with risk scoring for Bellman-Ford algorithms.
/// This is the "in-memory" representation for graph algorithms;
/// the compact TrustEdge remains the wire format.
pub const RiskEdge = struct {
    /// Source node index
    from: NodeId,
    /// Target node index
    to: NodeId,
    /// Risk score: negative = betrayal signal, positive = vouch
    /// Range: [-1.0, 1.0] where:
    ///   -1.0 = Confirmed betrayal (decade-level)
    ///   0.0 = Neutral/unknown
    ///   +1.0 = Maximum trust
    risk: f64,
    /// Temporal anchor for graph ordering (attosecond precision)
    timestamp: SovereignTimestamp,
    /// Nonce for path provenance (L0 sequence tied to trust transition)
    /// Enables: replay protection, exact path reconstruction, routing verification
    nonce: u64,
    /// Original trust level (for path verification)
    level: u8,
    /// Expiration timestamp
    expires_at: SovereignTimestamp,

    pub fn isBetrayal(self: RiskEdge) bool {
        return self.risk < 0.0;
    }

    pub fn isExpired(self: RiskEdge, current_time: SovereignTimestamp) bool {
        return current_time.isAfter(self.expires_at);
    }
};

/// Anomaly score from Bellman-Ford or Belief Propagation.
/// Normalized to [0, 1] where:
///   0.0 = No anomaly
///   0.7+ = P1 Alert (requires investigation)
///   0.9+ = P0 Critical (immediate action)
pub const AnomalyScore = struct {
    node: NodeId,
    score: f64,
    reason: Reason,

    pub const Reason = enum {
        none,
        negative_cycle, // Bellman-Ford
        low_coverage, // Gossip partition
        bp_divergence, // Belief Propagation
        pomcp_reject, // POMCP planning
    };

    pub fn isCritical(self: AnomalyScore) bool {
        return self.score >= 0.9;
    }

    pub fn isAlert(self: AnomalyScore) bool {
        return self.score >= 0.7;
    }
};

/// Graph structure for QVL algorithms.
/// Wraps edges and provides adjacency lookup.
pub const RiskGraph = struct {
    allocator: std.mem.Allocator,
    nodes: std.ArrayListUnmanaged(NodeId),
    edges: std.ArrayListUnmanaged(RiskEdge),
    /// Adjacency: node -> list of edge indices
    adjacency: std.AutoHashMapUnmanaged(NodeId, std.ArrayListUnmanaged(usize)),

    pub fn init(allocator: std.mem.Allocator) RiskGraph {
        return .{
            .allocator = allocator,
            .nodes = .{},
            .edges = .{},
            .adjacency = .{},
        };
    }

    pub fn deinit(self: *RiskGraph) void {
        self.nodes.deinit(self.allocator);
        self.edges.deinit(self.allocator);
        var it = self.adjacency.iterator();
        while (it.next()) |entry| {
            entry.value_ptr.deinit(self.allocator);
        }
        self.adjacency.deinit(self.allocator);
    }

    pub fn addNode(self: *RiskGraph, node: NodeId) !void {
        try self.nodes.append(self.allocator, node);
    }

    pub fn addEdge(self: *RiskGraph, edge: RiskEdge) !void {
        const edge_idx = self.edges.items.len;
        try self.edges.append(self.allocator, edge);

        // Update adjacency
        const entry = try self.adjacency.getOrPut(self.allocator, edge.from);
        if (!entry.found_existing) {
            entry.value_ptr.* = .{};
        }
        try entry.value_ptr.append(self.allocator, edge_idx);
    }

    pub fn neighbors(self: *const RiskGraph, node: NodeId) []const usize {
        if (self.adjacency.get(node)) |edges| {
            return edges.items;
        }
        return &[_]usize{};
    }

    pub fn nodeCount(self: *const RiskGraph) usize {
        return self.nodes.items.len;
    }

    pub fn edgeCount(self: *const RiskGraph) usize {
        return self.edges.items.len;
    }

    pub fn getEdge(self: *const RiskGraph, from: NodeId, to: NodeId) ?RiskEdge {
        if (self.adjacency.get(from)) |indices| {
            for (indices.items) |idx| {
                const edge = self.edges.items[idx];
                if (edge.to == to) return edge;
            }
        }
        return null;
    }
};

test "RiskGraph: basic operations" {
    const allocator = std.testing.allocator;
    var graph = RiskGraph.init(allocator);
    defer graph.deinit();

    try graph.addNode(0);
    try graph.addNode(1);
    try graph.addNode(2);

    const ts = SovereignTimestamp.fromSeconds(0, .system_boot);
    try graph.addEdge(.{ .from = 0, .to = 1, .risk = 0.5, .timestamp = ts, .nonce = 0, .level = 3, .expires_at = ts });
    try graph.addEdge(.{ .from = 1, .to = 2, .risk = -0.3, .timestamp = ts, .nonce = 1, .level = 2, .expires_at = ts }); // Betrayal

    try std.testing.expectEqual(graph.nodeCount(), 3);
    try std.testing.expectEqual(graph.edgeCount(), 2);
    try std.testing.expectEqual(graph.neighbors(0).len, 1);
    try std.testing.expect(graph.edges.items[1].isBetrayal());
}
