// Relationship-type matching and neighbor/rel iteration over a Snapshot:
// the CSR expand ranges, the Neighbors/Rels iter.Seq accessors, and the
// RelMatch pre-resolved filter for hot paths.

package chickpeas

import (
	
	

	
	
)

// RelRef is one relationship incident to a queried node, as yielded by
// Snapshot.Rels. It carries the other endpoint, the type, the direction
// relative to the queried node, and the CSR position for reading the rel's
// properties via Snapshot.RelProp -- valid in BOTH directions (an incoming
// rel's position is pre-mapped to where the property is stored).
type RelRef struct {
	Neighbor  NodeID
	Type      RelType
	Direction Direction
	Pos       uint32
}

// RelMatch is a resolved relationship-type filter. The common single-type
// filter matches with one comparison and no allocation; two or more types
// spill to a slice. Build with Snapshot.Match / MatchType / MatchAll.
type RelMatch struct {
	kind uint8 // 0 = all, 1 = one, 2 = many (possibly empty = match nothing)
	one  RelType
	many []RelType
	// tp is the single type's lazy typed-adjacency holder, populated by
	// Snapshot.Match (not the snapshot-less MatchType): traversals route
	// through the contiguous per-type view when it exists (typedadj.go).
	tp *typedPair
}

// MatchAll matches every relationship type.
func () RelMatch {
	return RelMatch{kind: 0}
}

// MatchType matches a single pre-resolved type, allocation-free.
func ( RelType) RelMatch {
	return RelMatch{kind: 1, one: }
}

// MatchNone matches no relationship type.
func () RelMatch {
	return RelMatch{kind: 2}
}

func ( RelMatch) ( RelType) bool {
	switch .kind {
	case 0:
		return true
	case 1:
		return .one == 
	}
	return slices.Contains(.many, )
}

// Match resolves relationship-type names to a reusable filter: resolve once
// and pass to the *Match traversal methods in a hot loop to skip the
// per-call string lookups. Zero names match every type; unknown names are
// dropped (an unresolvable name has no rels), so all-unknown matches
// nothing. The zero- and one-name paths are allocation-free, so the
// string-typed traversal conveniences stay cheap in hot loops.
func ( *Snapshot) ( ...string) RelMatch {
	switch len() {
	case 0:
		return MatchAll()
	case 1:
		if ,  := .RelType([0]);  {
			return RelMatch{kind: 1, one: , tp: .typedPairFor()}
		}
		return MatchNone()
	}
	return .matchMany()
}

// matchMany is Match's two-or-more-names path, split out so the common
// zero/one-name paths stay within the inlining budget.
func ( *Snapshot) ( []string) RelMatch {
	 := make([]RelType, 0, len())
	for ,  := range  {
		if ,  := .RelType();  {
			 = append(, )
		}
	}
	switch len() {
	case 0:
		return MatchNone()
	case 1:
		return MatchType([0])
	}
	return RelMatch{kind: 2, many: }
}

// relRange is the CSR rel-index range [start, end) for a node in a
// direction's offset array; empty for ids outside the id space.
func relRange( []uint32,  NodeID) (int, int) {
	 := int()
	if +1 >= len() {
		return 0, 0
	}
	return int([]), int([+1])
}

// Degree is the number of relationships incident to node in direction,
// any type -- an O(1) offset difference per side (Both sums the two).
// A runtime fan-out signal for adaptive anchor decisions; for a
// type-restricted count, count Neighbors instead.
func ( *Snapshot) ( NodeID,  Direction) int {
	 := 0
	if  == Outgoing ||  == Both {
		,  := relRange(.outOffsets, )
		 +=  - 
	}
	if  == Incoming ||  == Both {
		,  := relRange(.inOffsets, )
		 +=  - 
	}
	return 
}

// Neighbors iterates the neighbors of node in direction, restricted to the
// given relationship types (zero types match all). Direction Both yields
// matching outgoing neighbors then matching incoming ones; duplicate rels
// yield duplicate neighbors, so counting composes. For per-rel type or
// property access during traversal use Rels.
//
// Both accessors are thin inlinable closure constructors over
// neighborsYield, so a direct `for range` over them is allocation-free
// (the closure and its captures stay on the stack).
func ( *Snapshot) ( NodeID,  Direction,  ...string) iter.Seq[NodeID] {
	return func( func(NodeID) bool) {
		.neighborsYield(, , .Match(...), )
	}
}

// NeighborsMatch is Neighbors over a pre-resolved RelMatch, for hot loops.
func ( *Snapshot) ( NodeID,  Direction,  RelMatch) iter.Seq[NodeID] {
	return func( func(NodeID) bool) {
		.neighborsYield(, , , )
	}
}

// AppendNeighborsMatch appends node's matching dir neighbors to dst as a
// deduplicated ASCENDING id set -- the neighbor-ID surface contract
// (parallel same-type rels, or a rel seen from both directions under
// Both, contribute one entry). Per-relationship multiplicity in CSR
// order stays available on AppendNeighborsEach and the Rels iterators.
// Only the appended tail is sorted and compacted; dst's existing prefix
// is untouched.
func ( *Snapshot) ( []NodeID,  NodeID,  Direction,  RelMatch) []NodeID {
	 := len()
	 = .AppendNeighborsEach(, , , )
	 := [:]
	slices.Sort()
	return append([:], slices.Compact()...)
}

// AppendNeighborsEach appends one entry per matching dir relationship of
// node, in CSR order -- the traversal primitive behind pattern expansion,
// where relationship multiplicity and first-seen order are semantic. It
// walks the CSR ranges directly rather than through a yield closure:
// neighborsYield is too large to inline, so handing it a closure would
// heap-escape that closure on every call. The walk mirrors
// neighborsYield exactly, minus the early-stop the fill never needs.
func ( *Snapshot) ( []NodeID,  NodeID,  Direction,  RelMatch) []NodeID {
	if  == Outgoing ||  == Both {
		if  := .tp.view(true);  != nil {
			,  := relRange(.offsets, )
			 = append(, .nbrs[:]...)
		} else {
			,  := relRange(.outOffsets, )
			var  *typedRuns
			if - > runScanFloor {
				 = .tp.runs(true)
			}
			if  != nil {
				,  := .runRange()
				 = append(, .nbrs[:]...)
			} else {
				for  := ;  < ; ++ {
					if .matches(.outTypes[]) {
						 = append(, .outNbrs[])
					}
				}
			}
		}
	}
	if  == Incoming ||  == Both {
		if  := .tp.view(false);  != nil {
			,  := relRange(.offsets, )
			 = append(, .nbrs[:]...)
		} else {
			,  := relRange(.inOffsets, )
			var  *typedRuns
			if - > runScanFloor {
				 = .tp.runs(false)
			}
			if  != nil {
				,  := .runRange()
				 = append(, .nbrs[:]...)
			} else {
				for  := ;  < ; ++ {
					if .matches(.inTypes[]) {
						 = append(, .inNbrs[])
					}
				}
			}
		}
	}
	return 
}

// neighborsYield walks the CSR ranges, yielding matching neighbors. The
// yield parameter never escapes, keeping range-over-func callers
// allocation-free.
func ( *Snapshot) ( NodeID,  Direction,  RelMatch,  func(NodeID) bool) {
	if  == Outgoing ||  == Both {
		if  := .tp.view(true);  != nil {
			,  := relRange(.offsets, )
			for  := ;  < ; ++ {
				if !(.nbrs[]) {
					return
				}
			}
		} else {
			,  := relRange(.outOffsets, )
			var  *typedRuns
			if - > runScanFloor {
				 = .tp.runs(true)
			}
			if  != nil {
				,  := .runRange()
				for  := ;  < ; ++ {
					if !(.nbrs[]) {
						return
					}
				}
			} else {
				for  := ;  < ; ++ {
					if .matches(.outTypes[]) && !(.outNbrs[]) {
						return
					}
				}
			}
		}
	}
	if  == Incoming ||  == Both {
		if  := .tp.view(false);  != nil {
			,  := relRange(.offsets, )
			for  := ;  < ; ++ {
				if !(.nbrs[]) {
					return
				}
			}
		} else {
			,  := relRange(.inOffsets, )
			var  *typedRuns
			if - > runScanFloor {
				 = .tp.runs(false)
			}
			if  != nil {
				,  := .runRange()
				for  := ;  < ; ++ {
					if !(.nbrs[]) {
						return
					}
				}
			} else {
				for  := ;  < ; ++ {
					if .matches(.inTypes[]) && !(.inNbrs[]) {
						return
					}
				}
			}
		}
	}
}

// Rels iterates the relationships incident to node in direction, each
// carrying the CSR position for property reads (see RelRef), outgoing
// matches then incoming ones. Zero types match all. Like Neighbors,
// both accessors are thin inlinable closure constructors, so a direct
// `for range` over them is allocation-free.
func ( *Snapshot) ( NodeID,  Direction,  ...string) iter.Seq[RelRef] {
	return func( func(RelRef) bool) {
		.relsYield(, , .Match(...), )
	}
}

// RelsMatch is Rels over a pre-resolved RelMatch, for hot loops.
func ( *Snapshot) ( NodeID,  Direction,  RelMatch) iter.Seq[RelRef] {
	return func( func(RelRef) bool) {
		.relsYield(, , , )
	}
}

// relsYield walks the CSR ranges, yielding matching RelRefs; yield never
// escapes.
func ( *Snapshot) ( NodeID,  Direction,  RelMatch,  func(RelRef) bool) {
	if  == Outgoing ||  == Both {
		if  := .tp.view(true);  != nil {
			,  := relRange(.offsets, )
			for  := ;  < ; ++ {
				if !(RelRef{Neighbor: .nbrs[], Type: .one, Direction: Outgoing, Pos: .poss[]}) {
					return
				}
			}
		} else {
			,  := relRange(.outOffsets, )
			var  *typedRuns
			if - > runScanFloor {
				 = .tp.runs(true)
			}
			if  != nil {
				,  := .runRange()
				for  := ;  < ; ++ {
					if !(RelRef{Neighbor: .nbrs[], Type: .one, Direction: Outgoing, Pos: .poss[]}) {
						return
					}
				}
			} else {
				for  := ;  < ; ++ {
					 := .outTypes[]
					if !.matches() {
						continue
					}
					if !(RelRef{Neighbor: .outNbrs[], Type: , Direction: Outgoing, Pos: uint32()}) {
						return
					}
				}
			}
		}
	}
	if  == Incoming ||  == Both {
		if  := .tp.view(false);  != nil {
			,  := relRange(.offsets, )
			for  := ;  < ; ++ {
				if !(RelRef{Neighbor: .nbrs[], Type: .one, Direction: Incoming, Pos: .poss[]}) {
					return
				}
			}
		} else {
			,  := relRange(.inOffsets, )
			var  *typedRuns
			if - > runScanFloor {
				 = .tp.runs(false)
			}
			if  != nil {
				,  := .runRange()
				for  := ;  < ; ++ {
					if !(RelRef{Neighbor: .nbrs[], Type: .one, Direction: Incoming, Pos: .poss[]}) {
						return
					}
				}
			} else {
				 := .getInToOut() // nil (no rel props) leaves raw positions
				for  := ;  < ; ++ {
					 := .inTypes[]
					if !.matches() {
						continue
					}
					// Map the incoming position to where the property is stored.
					 := uint32()
					if  < len() {
						 = []
					}
					if !(RelRef{Neighbor: .inNbrs[], Type: , Direction: Incoming, Pos: }) {
						return
					}
				}
			}
		}
	}
}

// FirstNeighbor is the first neighbor of node along the given types in
// direction -- the single-step lookup idiom (a message's creator, a
// person's city). ok is false when there is none.
func ( *Snapshot) ( NodeID,  Direction,  ...string) (NodeID, bool) {
	for  := range .Neighbors(, , ...) {
		return , true
	}
	return 0, false
}

// Step is one link of a Follow chain.
type Step struct {
	Dir     Direction
	RelType string
}

// Follow walks a fixed chain of single-rel steps from start, taking the
// first neighbor at each step (e.g. person -> city -> country); ok is false
// as soon as a step has no neighbor.
func ( *Snapshot) ( NodeID,  ...Step) (NodeID, bool) {
	 := 
	for ,  := range  {
		,  := .FirstNeighbor(, .Dir, .RelType)
		if ! {
			return 0, false
		}
		 = 
	}
	return , true
}

// HasRel reports whether node has at least one neighbor along the given
// types in direction -- the existence predicate behind "has any X rel".
func ( *Snapshot) ( NodeID,  Direction,  ...string) bool {
	,  := .FirstNeighbor(, , ...)
	return 
}

// NeighborsInSet iterates the typed neighbors of node that are members of
// set -- a label's nodes, a search result, or any precomputed set. Duplicate
// rels are preserved, so it composes with counting.
func ( *Snapshot) ( NodeID,  Direction,  *nodeset.Set,  ...string) iter.Seq[NodeID] {
	return func( func(NodeID) bool) {
		.neighborsInSetYield(, , , .Match(...), )
	}
}

// neighborsInSetYield is NeighborsInSet's non-inlined walker; yield
// never escapes.
func ( *Snapshot) ( NodeID,  Direction,  *nodeset.Set,  RelMatch,  func(NodeID) bool) {
	.neighborsYield(, , , func( NodeID) bool {
		return !.Contains() || ()
	})
}

// HasNeighborWithProperty reports whether any typed neighbor of node in
// direction has property key equal to value (any of string, int, int32,
// int64, float64, bool, or Value). The comparison value resolves to a
// Value once, outside the neighbor scan.
func ( *Snapshot) ( NodeID,  Direction,  string,  any,  ...string) bool {
	,  := .PropertyKey()
	if ! {
		return false
	}
	,  := .valueFromAny()
	if ! {
		return false
	}
	,  := .columns[]
	if ! {
		return false
	}
	for  := range .Neighbors(, , ...) {
		if ,  := .Get();  &&  ==  {
			return true
		}
	}
	return false
}

// ParNeighborFold folds node's typed neighbors in parallel, merging the
// per-chunk results -- NeighborsMatch composed with a parallel fold. Use
// when each neighbor drives substantial independent work; for light
// per-neighbor work iterate sequentially. The reduce runs in ascending
// chunk order, so the result is deterministic for any associative reduce.
func [ any]( *Snapshot,  NodeID,  Direction,  RelMatch,
	 func() ,  func( ,  NodeID) ,  func(,  ) )  {
	var  []NodeID
	for  := range .NeighborsMatch(, , ) {
		 = append(, )
	}
	return parallel.Fold(len(), ,
		func( ,  int)  { return (, []) },
		)
}