//go:build !(js && wasm) package database import ( "sort" ) // GraphResult contains depth-organized traversal results for graph queries. // It tracks pubkeys and events discovered at each depth level, ensuring // each entity appears only at the depth where it was first discovered. type GraphResult struct { // PubkeysByDepth maps depth -> pubkeys first discovered at that depth. // Each pubkey appears ONLY in the array for the depth where it was first seen. // Depth 1 = direct connections, Depth 2 = connections of connections, etc. PubkeysByDepth map[int][]string // EventsByDepth maps depth -> event IDs discovered at that depth. // Used for thread traversal queries. EventsByDepth map[int][]string // FirstSeenPubkey tracks which depth each pubkey was first discovered. // Key is pubkey hex, value is the depth (1-indexed). FirstSeenPubkey map[string]int // FirstSeenEvent tracks which depth each event was first discovered. // Key is event ID hex, value is the depth (1-indexed). FirstSeenEvent map[string]int // TotalPubkeys is the count of unique pubkeys discovered across all depths. TotalPubkeys int // TotalEvents is the count of unique events discovered across all depths. TotalEvents int // InboundRefs tracks inbound references (events that reference discovered items). // Structure: kind -> target_id -> []referencing_event_ids InboundRefs map[uint16]map[string][]string // OutboundRefs tracks outbound references (events referenced by discovered items). // Structure: kind -> source_id -> []referenced_event_ids OutboundRefs map[uint16]map[string][]string } // NewGraphResult creates a new initialized GraphResult. func NewGraphResult() *GraphResult { return &GraphResult{ PubkeysByDepth: make(map[int][]string), EventsByDepth: make(map[int][]string), FirstSeenPubkey: make(map[string]int), FirstSeenEvent: make(map[string]int), InboundRefs: make(map[uint16]map[string][]string), OutboundRefs: make(map[uint16]map[string][]string), } } // AddPubkeyAtDepth adds a pubkey to the result at the specified depth if not already seen. // Returns true if the pubkey was added (first time seen), false if already exists. func (r *GraphResult) AddPubkeyAtDepth(pubkeyHex string, depth int) bool { if _, exists := r.FirstSeenPubkey[pubkeyHex]; exists { return false } r.FirstSeenPubkey[pubkeyHex] = depth r.PubkeysByDepth[depth] = append(r.PubkeysByDepth[depth], pubkeyHex) r.TotalPubkeys++ return true } // AddEventAtDepth adds an event ID to the result at the specified depth if not already seen. // Returns true if the event was added (first time seen), false if already exists. func (r *GraphResult) AddEventAtDepth(eventIDHex string, depth int) bool { if _, exists := r.FirstSeenEvent[eventIDHex]; exists { return false } r.FirstSeenEvent[eventIDHex] = depth r.EventsByDepth[depth] = append(r.EventsByDepth[depth], eventIDHex) r.TotalEvents++ return true } // HasPubkey returns true if the pubkey has been discovered at any depth. func (r *GraphResult) HasPubkey(pubkeyHex string) bool { _, exists := r.FirstSeenPubkey[pubkeyHex] return exists } // HasEvent returns true if the event has been discovered at any depth. func (r *GraphResult) HasEvent(eventIDHex string) bool { _, exists := r.FirstSeenEvent[eventIDHex] return exists } // GetPubkeyDepth returns the depth at which a pubkey was first discovered. // Returns 0 if the pubkey was not found. func (r *GraphResult) GetPubkeyDepth(pubkeyHex string) int { return r.FirstSeenPubkey[pubkeyHex] } // GetEventDepth returns the depth at which an event was first discovered. // Returns 0 if the event was not found. func (r *GraphResult) GetEventDepth(eventIDHex string) int { return r.FirstSeenEvent[eventIDHex] } // GetDepthsSorted returns all depths that have pubkeys, sorted ascending. func (r *GraphResult) GetDepthsSorted() []int { depths := make([]int, 0, len(r.PubkeysByDepth)) for d := range r.PubkeysByDepth { depths = append(depths, d) } sort.Ints(depths) return depths } // GetEventDepthsSorted returns all depths that have events, sorted ascending. func (r *GraphResult) GetEventDepthsSorted() []int { depths := make([]int, 0, len(r.EventsByDepth)) for d := range r.EventsByDepth { depths = append(depths, d) } sort.Ints(depths) return depths } // ToDepthArrays converts the result to the response format: array of arrays. // Index 0 = depth 1 pubkeys, Index 1 = depth 2 pubkeys, etc. // Empty arrays are included for depths with no pubkeys to maintain index alignment. func (r *GraphResult) ToDepthArrays() [][]string { if len(r.PubkeysByDepth) == 0 { return [][]string{} } // Find the maximum depth maxDepth := 0 for d := range r.PubkeysByDepth { if d > maxDepth { maxDepth = d } } // Create result array with entries for each depth result := make([][]string, maxDepth) for i := 0; i < maxDepth; i++ { depth := i + 1 // depths are 1-indexed if pubkeys, exists := r.PubkeysByDepth[depth]; exists { result[i] = pubkeys } else { result[i] = []string{} // Empty array for depths with no pubkeys } } return result } // ToEventDepthArrays converts event results to the response format: array of arrays. // Index 0 = depth 1 events, Index 1 = depth 2 events, etc. func (r *GraphResult) ToEventDepthArrays() [][]string { if len(r.EventsByDepth) == 0 { return [][]string{} } maxDepth := 0 for d := range r.EventsByDepth { if d > maxDepth { maxDepth = d } } result := make([][]string, maxDepth) for i := 0; i < maxDepth; i++ { depth := i + 1 if events, exists := r.EventsByDepth[depth]; exists { result[i] = events } else { result[i] = []string{} } } return result } // AddInboundRef records an inbound reference from a referencing event to a target. func (r *GraphResult) AddInboundRef(kind uint16, targetIDHex string, referencingEventIDHex string) { if r.InboundRefs[kind] == nil { r.InboundRefs[kind] = make(map[string][]string) } r.InboundRefs[kind][targetIDHex] = append(r.InboundRefs[kind][targetIDHex], referencingEventIDHex) } // AddOutboundRef records an outbound reference from a source event to a referenced event. func (r *GraphResult) AddOutboundRef(kind uint16, sourceIDHex string, referencedEventIDHex string) { if r.OutboundRefs[kind] == nil { r.OutboundRefs[kind] = make(map[string][]string) } r.OutboundRefs[kind][sourceIDHex] = append(r.OutboundRefs[kind][sourceIDHex], referencedEventIDHex) } // RefAggregation represents aggregated reference data for a single target/source. type RefAggregation struct { // TargetEventID is the event ID being referenced (for inbound) or referencing (for outbound) TargetEventID string // TargetAuthor is the author pubkey of the target event (if known) TargetAuthor string // TargetDepth is the depth at which this target was discovered in the graph TargetDepth int // RefKind is the kind of the referencing events RefKind uint16 // RefCount is the number of references to/from this target RefCount int // RefEventIDs is the list of event IDs that reference this target RefEventIDs []string } // GetInboundRefsSorted returns inbound refs for a kind, sorted by count descending. func (r *GraphResult) GetInboundRefsSorted(kind uint16) []RefAggregation { kindRefs := r.InboundRefs[kind] if kindRefs == nil { return nil } aggs := make([]RefAggregation, 0, len(kindRefs)) for targetID, refs := range kindRefs { agg := RefAggregation{ TargetEventID: targetID, TargetDepth: r.GetEventDepth(targetID), RefKind: kind, RefCount: len(refs), RefEventIDs: refs, } aggs = append(aggs, agg) } // Sort by count descending sort.Slice(aggs, func(i, j int) bool { return aggs[i].RefCount > aggs[j].RefCount }) return aggs } // GetOutboundRefsSorted returns outbound refs for a kind, sorted by count descending. func (r *GraphResult) GetOutboundRefsSorted(kind uint16) []RefAggregation { kindRefs := r.OutboundRefs[kind] if kindRefs == nil { return nil } aggs := make([]RefAggregation, 0, len(kindRefs)) for sourceID, refs := range kindRefs { agg := RefAggregation{ TargetEventID: sourceID, TargetDepth: r.GetEventDepth(sourceID), RefKind: kind, RefCount: len(refs), RefEventIDs: refs, } aggs = append(aggs, agg) } sort.Slice(aggs, func(i, j int) bool { return aggs[i].RefCount > aggs[j].RefCount }) return aggs } // GetAllPubkeys returns all pubkeys discovered across all depths. func (r *GraphResult) GetAllPubkeys() []string { all := make([]string, 0, r.TotalPubkeys) for _, pubkeys := range r.PubkeysByDepth { all = append(all, pubkeys...) } return all } // GetAllEvents returns all event IDs discovered across all depths. func (r *GraphResult) GetAllEvents() []string { all := make([]string, 0, r.TotalEvents) for _, events := range r.EventsByDepth { all = append(all, events...) } return all } // GetPubkeysAtDepth returns pubkeys at a specific depth, or empty slice if none. func (r *GraphResult) GetPubkeysAtDepth(depth int) []string { if pubkeys, exists := r.PubkeysByDepth[depth]; exists { return pubkeys } return []string{} } // GetEventsAtDepth returns events at a specific depth, or empty slice if none. func (r *GraphResult) GetEventsAtDepth(depth int) []string { if events, exists := r.EventsByDepth[depth]; exists { return events } return []string{} } // Interface methods for external package access (e.g., pkg/protocol/graph) // These allow the graph executor to extract data without direct struct access. // GetPubkeysByDepth returns the PubkeysByDepth map for external access. func (r *GraphResult) GetPubkeysByDepth() map[int][]string { return r.PubkeysByDepth } // GetEventsByDepth returns the EventsByDepth map for external access. func (r *GraphResult) GetEventsByDepth() map[int][]string { return r.EventsByDepth } // GetTotalPubkeys returns the total pubkey count for external access. func (r *GraphResult) GetTotalPubkeys() int { return r.TotalPubkeys } // GetTotalEvents returns the total event count for external access. func (r *GraphResult) GetTotalEvents() int { return r.TotalEvents }