// Neighborhood aggregation and co-occurrence kernels: forest roots of a
// functional rel chain (RootsVia/RootVia/NeighborVia), one-mode network
// folding (FoldVia), seeded co-occurrence (CoOccurring), and neighbor /
// common-neighbor histograms (NeighborCounts, CommonNeighbors,
// CommonNeighborCounts). NeighborGroups lives in neighborgroups.go.

package chickpeas

import (
	
)

// RootsVia is a built forest-root array: index it by node id for the
// terminal of that node's functional-relationship chain. Shared and
// immutable -- hot loops hold it and index lock-free.
type RootsVia []NodeID

// NoNeighbor is the sentinel a NeighborVia array holds for a node with no
// such neighbor.
const NoNeighbor = NodeID(^uint32(0))

type rootsKey struct {
	dir Direction
	t   RelType
}

// RootsVia is the per-node forest-root array for the functional relType
// chain in dir: roots[node] is the node reached by following the single
// relType rel until one with no such rel (a terminal node maps to itself).
// Built once per (direction, type) with path compression, then cached --
// call once and index the slice, rather than RootVia per node. Intended
// for a rel that is functional in dir; malformed data (multiple such rels,
// or a cycle) follows the first neighbor in CSR order and is broken by a
// depth cap, resolving deterministically.
func ( *Snapshot) ( RelType,  Direction) RootsVia {
	 := rootsKey{dir: , t: }
	.rootsMu.Lock()
	,  := .rootsViaIndex[]
	.rootsMu.Unlock()
	if  {
		return 
	}
	// Build outside the lock; a racing build is discarded (identical).
	 := .buildRootsVia(, )
	.rootsMu.Lock()
	defer .rootsMu.Unlock()
	if ,  := .rootsViaIndex[];  {
		return 
	}
	.rootsViaIndex[] = 
	return 
}

// RootVia is the terminal of node's functional relType chain in dir (a
// terminal node maps to itself) -- a convenience over RootsVia; in a hot
// loop index the array instead.
func ( *Snapshot) ( NodeID,  RelType,  Direction) NodeID {
	 := .RootsVia(, )
	if int() < len() {
		return []
	}
	return 
}

// terminalKey keys the terminal-exclusivity cache.
type terminalKey struct {
	dir Direction
	t   RelType
	l   Label
}

// ChainCollapseVia reports whether an unbounded zero-minimum
// reachable-set expansion over relType in dir, filtered to label, is
// equivalent to one RootsVia lookup -- and returns the root array when it
// is. Two structural facts, each verified (never assumed) and cached:
// the type must be FUNCTIONAL in dir (every node has at most one such
// rel, so the reachable set is exactly the ancestor chain), and label
// must be TERMINAL-EXCLUSIVE for it (no labeled node has such a rel
// outgoing in dir, so only a chain's terminal can carry the label --
// making "reachable nodes with the label" either the root or nothing).
func ( *Snapshot) ( string,  Direction,  string) (RootsVia, bool) {
	if  == Both {
		return nil, false
	}
	,  := .RelType()
	if ! {
		return nil, false
	}
	,  := .Label()
	if ! {
		return nil, false
	}
	if !.functionalCached(, ) {
		return nil, false
	}
	 := terminalKey{dir: , t: , l: }
	.rootsMu.Lock()
	,  := .terminalOnly[]
	.rootsMu.Unlock()
	if ! {
		 = .checkTerminalOnly(, , )
		.rootsMu.Lock()
		.terminalOnly[] = 
		.rootsMu.Unlock()
	}
	if ! {
		return nil, false
	}
	return .RootsVia(, ), true
}

// FunctionalVia reports whether relType is functional in dir -- every
// node has at most one such rel, so its reachability structure is a
// forest of chains (RootsVia's precondition). Verified once and cached.
func ( *Snapshot) ( string,  Direction) bool {
	if  == Both {
		return false
	}
	,  := .RelType()
	if ! {
		return false
	}
	return .functionalCached(, )
}

// functionalCached is FunctionalVia's verify-once core.
func ( *Snapshot) ( RelType,  Direction) bool {
	.rootsMu.Lock()
	,  := .functionalVia[rootsKey{dir: , t: }]
	.rootsMu.Unlock()
	if  {
		return 
	}
	 = .checkFunctionalVia(, )
	.rootsMu.Lock()
	.functionalVia[rootsKey{dir: , t: }] = 
	.rootsMu.Unlock()
	return 
}

// checkFunctionalVia verifies every node has at most one relType rel in
// dir. One CSR pass.
func ( *Snapshot) ( RelType,  Direction) bool {
	,  := .outOffsets, .outTypes
	if  == Incoming {
		,  = .inOffsets, .inTypes
	}
	for  := 0; +1 < len(); ++ {
		 := 0
		for  := [];  < [+1]; ++ {
			if [] ==  {
				++
				if  > 1 {
					return false
				}
			}
		}
	}
	return true
}

// checkTerminalOnly verifies no l-labeled node has a relType rel
// outgoing in dir. One pass over the label's members.
func ( *Snapshot) ( RelType,  Direction,  Label) bool {
	,  := .labelIndex[]
	if ! {
		return true
	}
	,  := .outOffsets, .outTypes
	if  == Incoming {
		,  = .inOffsets, .inTypes
	}
	for  := range .Iter() {
		if int()+1 >= len() {
			continue
		}
		for  := [];  < [+1]; ++ {
			if [] ==  {
				return false
			}
		}
	}
	return true
}

// NeighborVia is the single neighbor each node reaches via the functional
// relType in dir (one hop -- e.g. a message's hasCreator); the depth-1
// sibling of RootsVia. Node-indexed; a node with no such neighbor maps to
// NoNeighbor. Built fresh, not cached.
func ( *Snapshot) ( RelType,  Direction) RootsVia {
	 := int(.CSRIDSpace())
	 := MatchType()
	 := make(RootsVia, )
	for  := range NodeID() {
		[] = NoNeighbor
		for  := range .NeighborsMatch(, , ) {
			[] = 
			break
		}
	}
	return 
}

// buildRootsVia resolves every node's chain terminal once via path
// compression; a depth cap of the id-space size breaks any malformed
// cycle, so the build always terminates deterministically.
func ( *Snapshot) ( Direction,  RelType) RootsVia {
	 := int(.CSRIDSpace())
	 := MatchType()
	const  = NodeID(^uint32(0))
	 := make(RootsVia, )
	for  := range  {
		[] = 
	}
	var  []NodeID
	for  := range NodeID() {
		if [] !=  {
			continue
		}
		 = [:0]
		 := 
		var  NodeID
		for {
			if  := [];  !=  {
				 = 
				break
			}
			,  := .FirstNeighborMatch(, , )
			// path length <= n bounds a malformed cycle (a valid chain has
			// at most n-1 rels, so this never trips on a forest).
			if ! || len() >  {
				 = 
				break
			}
			 = append(, )
			 = 
		}
		for ,  := range  {
			[] = 
		}
		[] = 
	}
	return 
}

// FirstNeighborMatch is FirstNeighbor over a pre-resolved RelMatch.
func ( *Snapshot) ( NodeID,  Direction,  RelMatch) (NodeID, bool) {
	for  := range .NeighborsMatch(, , ) {
		return , true
	}
	return 0, false
}

// NodePair is an unordered node pair (Lo <= Hi), the key of FoldVia.
type NodePair struct {
	Lo, Hi NodeID
}

// FoldVia folds the m-matched rels (in dir) into a weighted node-pair map
// by projecting both endpoints of each rel through projection -- the
// one-mode / bipartite projection ("network folding") of a relation onto a
// derived node set. For every matched rel a -> b, a' = projection[a] and
// b' = projection[b] add one to the unordered pair count; self-pairs and
// endpoints projecting to NoNeighbor are skipped. projection is a flat
// node -> node array (a NeighborVia or RootsVia). Runs in parallel over
// the id space, per-chunk maps merged small-into-large.
func ( *Snapshot) ( RelMatch,  Direction,  []NodeID) map[NodePair]uint64 {
	 := int(.CSRIDSpace())
	return parallel.Fold(,
		func() map[NodePair]uint64 { return map[NodePair]uint64{} },
		func( map[NodePair]uint64,  int) map[NodePair]uint64 {
			if  >= len() {
				return 
			}
			 := []
			if  == NoNeighbor {
				return 
			}
			for  := range .NeighborsMatch(NodeID(), , ) {
				if int() >= len() {
					continue
				}
				 := []
				if  == NoNeighbor ||  ==  {
					continue
				}
				 := NodePair{Lo: min(, ), Hi: max(, )}
				[]++
			}
			return 
		},
		func(,  map[NodePair]uint64) map[NodePair]uint64 {
			if len() < len() {
				,  = , 
			}
			for ,  := range  {
				[] += 
			}
			return 
		})
}

// CoWeight selects how CoOccurring accumulates each co-occurring node's
// weight (declarative -- the kernel needs no per-element callback).
type CoWeight struct {
	distinct    bool
	distinctKey string
}

// CoCount weights by the number of shared centers (co-occurrence events).
func () CoWeight {
	return CoWeight{}
}

// CoDistinct weights by the number of distinct values of the property key
// read off each shared center (e.g. distinct co-occurrence days); a center
// lacking the key contributes nothing.
func ( string) CoWeight {
	return CoWeight{distinct: true, distinctKey: }
}

// CoOccurring is seeded co-occurrence -- one-mode projection by shared
// neighbor: from seed over the m-matched rels, the nodes sharing a
// neighbor with seed (seed -(m,dir)-> centers -(m,reversed)-> others),
// seed itself excluded, each weighted per w. The seeded row of the
// node-node co-occurrence matrix; the by-shared-neighbor complement of
// FoldVia's by-rel-endpoint projection.
func ( *Snapshot) ( NodeID,  RelMatch,  Direction,  CoWeight) map[NodeID]uint64 {
	 := .Reverse()
	if !.distinct {
		 := map[NodeID]uint64{}
		for  := range .NeighborsMatch(, , ) {
			for  := range .NeighborsMatch(, , ) {
				if  !=  {
					[]++
				}
			}
		}
		return 
	}
	,  := .PropertyKey(.distinctKey)
	if ! {
		return map[NodeID]uint64{}
	}
	,  := .columns[]
	if ! {
		return map[NodeID]uint64{}
	}
	 := map[NodeID]map[Value]struct{}{}
	for  := range .NeighborsMatch(, , ) {
		,  := .Get()
		if ! {
			continue
		}
		for  := range .NeighborsMatch(, , ) {
			if  ==  {
				continue
			}
			,  := []
			if ! {
				 = map[Value]struct{}{}
				[] = 
			}
			[] = struct{}{}
		}
	}
	 := make(map[NodeID]uint64, len())
	for ,  := range  {
		[] = uint64(len())
	}
	return 
}