// Unweighted traversal search: reachability, (bidirectional) BFS over node
// sets with nil-able node/rel filters, hop distances, and bounded
// neighborhoods. The distance kernels reuse generation-stamped dense
// scratch from a pool, so repeated searches on a large graph avoid an O(n)
// clear (and allocation) per call.

package chickpeas

import (
	

	
)

// NoMaxDepth removes the depth bound of a search.
const NoMaxDepth = -1

// NodeFilter gates a node's participation in a search; nil means no filter.
type NodeFilter func(node NodeID, g *Snapshot) bool

// RelFilter gates following a rel, receiving its stored (source, target)
// endpoints, type, and CSR position; nil means no filter.
type RelFilter func(from, to NodeID, t RelType, pos uint32, g *Snapshot) bool

// CanReach reports whether to is reachable from from over the m-matched
// rels in dir, within maxDepth hops (NoMaxDepth = unbounded).
func ( *Snapshot) (,  NodeID,  Direction,  RelMatch,  int) bool {
	if  ==  {
		return true
	}
	 := nodeset.Of()
	 := [][2]uint32{{, 0}}
	for len() > 0 {
		,  := [0][0], [0][1]
		 = [1:]
		if  >= 0 && int() >=  {
			continue
		}
		for  := range .NeighborsMatch(, , ) {
			if  ==  {
				return true
			}
			if .Insert() {
				 = append(, [2]uint32{,  + 1})
			}
		}
	}
	return false
}

// bfsStep expands one node of a BFS frontier, shared by BFS and both passes
// of BidirectionalBFS. backward flips the (from, to) the rel filter sees.
func ( *Snapshot) ( NodeID,  uint32,  Direction,  RelMatch,
	 NodeFilter,  RelFilter,  bool,
	, ,  *nodeset.Set,  *[][2]uint32) {
	for  := range .RelsMatch(, , ) {
		// The rel filter sees traversal orientation -- (current, neighbor)
		// on the forward pass, flipped on the backward pass -- matching the
		// Rust kernel.
		,  := , .Neighbor
		if  {
			,  = .Neighbor, 
		}
		if  != nil && !(, , .Type, .Pos, ) {
			continue
		}
		// The node filter applies before recording/marking, so a rejected
		// node never participates regardless of which frontier reaches it.
		if  != nil && !(.Neighbor, ) {
			continue
		}
		if  != nil && .Contains(.Neighbor) {
			// Meeting point: always record the connecting rel; enqueue only
			// if newly visited.
			.Insert(.Pos)
			if .Insert(.Neighbor) {
				* = append(*, [2]uint32{.Neighbor,  + 1})
			}
			continue
		}
		if  != nil {
			if .Insert(.Neighbor) {
				.Insert(.Pos)
				* = append(*, [2]uint32{.Neighbor,  + 1})
			}
			continue
		}
		.Insert(.Pos)
		if .Insert(.Neighbor) {
			* = append(*, [2]uint32{.Neighbor,  + 1})
		}
	}
}

// BFS traverses from the start set over the m-matched rels in dir,
// returning every node visited and every rel CSR position traversed.
// Filters gate participation (nil = none); maxDepth bounds the hop count
// (NoMaxDepth = unbounded).
func ( *Snapshot) ( *nodeset.Set,  Direction,  RelMatch,
	 NodeFilter,  RelFilter,  int) (,  *nodeset.Set) {
	,  = nodeset.New(), nodeset.New()
	var  [][2]uint32
	for  := range .Iter() {
		if ( == nil || (, )) && .Insert() {
			 = append(, [2]uint32{, 0})
		}
	}
	for len() > 0 {
		,  := [0][0], [0][1]
		 = [1:]
		if  >= 0 && int() >=  {
			continue
		}
		.bfsStep(, , , , , , false, , , nil, &)
	}
	return , 
}

// BidirectionalBFS searches from the source and target sets
// simultaneously, meeting in the middle: the forward pass follows dir, the
// backward pass its reverse. Returns the nodes reached by BOTH sides (the
// meeting set) and the union of rel positions either side traversed; both
// empty when the sets don't connect. An immediate source/target overlap
// returns it with no rels.
func ( *Snapshot) (,  *nodeset.Set,  Direction,  RelMatch,
	 NodeFilter,  RelFilter,  int) (,  *nodeset.Set) {
	,  := nodeset.New(), nodeset.New()
	,  := nodeset.New(), nodeset.New()
	var ,  [][2]uint32
	for  := range .Iter() {
		if  == nil || (, ) {
			.Insert()
			 = append(, [2]uint32{, 0})
		}
	}
	for  := range .Iter() {
		if  == nil || (, ) {
			.Insert()
			 = append(, [2]uint32{, 0})
		}
	}
	if  := .And(); !.IsEmpty() {
		return , nodeset.New()
	}
	 := .Reverse()

	 := func( *[][2]uint32, , ,  *nodeset.Set,  Direction,  bool) {
		for range len(*) { // one level
			,  := (*)[0][0], (*)[0][1]
			* = (*)[1:]
			if  >= 0 && int() >=  {
				continue
			}
			.bfsStep(, , , , , , , , , , )
		}
	}
	for len() > 0 || len() > 0 {
		(&, , , , , false)
		(&, , , , , true)
	}

	 := .And()
	if .IsEmpty() {
		return nodeset.New(), nodeset.New()
	}
	// The rel union covers everything both sides visited, not only rels
	// between meeting nodes (filtering those would need path
	// reconstruction) -- same contract as the Rust kernel.
	return , .Or()
}

// searchScratch is the generation-stamped dense visited/distance scratch
// behind BFSDistances and Neighborhood: a node counts as visited this run
// iff gen[node] == cur, avoiding an O(n) clear per call. Pooled on the
// snapshot so concurrent searches each take their own.
type searchScratch struct {
	gen  []uint32
	dist []uint32
	cur  uint32
}

var scratchPool = sync.Pool{New: func() any { return &searchScratch{} }}

func takeScratch( int) *searchScratch {
	 := scratchPool.Get().(*searchScratch)
	if len(.gen) <  {
		.gen = make([]uint32, )
		.dist = make([]uint32, )
		.cur = 0
	}
	.cur++
	if .cur == 0 { // wrapped: reset the stamps once
		clear(.gen)
		.cur = 1
	}
	return 
}

// BFSDistances is unweighted single-source BFS returning the hop distance
// from start to every reached node (start itself at 0), bounded by
// maxDepth (NoMaxDepth = the whole component). Cheaper than Dijkstra with
// a unit weight when only hop counts are needed.
func ( *Snapshot) ( NodeID,  Direction,  RelMatch,  int) map[NodeID]uint32 {
	 := int(.CSRIDSpace())
	// A start outside the CSR id space is not a node in this graph.
	if int() >=  {
		return map[NodeID]uint32{}
	}
	 := takeScratch()
	defer scratchPool.Put()

	.gen[] = .cur
	.dist[] = 0
	 := []NodeID{}
	 := []NodeID{}
	 := uint32(0)
	for len() > 0 {
		if  >= 0 && int() >=  {
			break
		}
		++
		var  []NodeID
		for ,  := range  {
			for  := range .NeighborsMatch(, , ) {
				if .gen[] != .cur {
					.gen[] = .cur
					.dist[] = 
					 = append(, )
					 = append(, )
				}
			}
		}
		 = 
	}
	 := make(map[NodeID]uint32, len())
	for ,  := range  {
		[] = .dist[]
	}
	return 
}

// Neighborhood is the set of nodes whose hop distance from seed lies in
// the closed range [loHops, hiHops] -- 1..2 is "one or two hops out"
// (excludes seed), 0..2 includes it, 2..2 is exactly two hops. Returned as
// a set so membership is O(1) and intersecting with another set is one
// bitmap op.
func ( *Snapshot) ( NodeID,  Direction,  RelMatch, ,  uint32) *nodeset.Set {
	 := nodeset.New()
	 := int(.CSRIDSpace())
	if  >  || int() >=  {
		return 
	}
	 := takeScratch()
	defer scratchPool.Put()

	.gen[] = .cur
	if  == 0 {
		.Insert()
	}
	 := []NodeID{}
	for  := uint32(1);  <=  && len() > 0; ++ {
		var  []NodeID
		for ,  := range  {
			for  := range .NeighborsMatch(, , ) {
				if .gen[] != .cur {
					.gen[] = .cur
					 = append(, )
					if  >=  {
						.Insert()
					}
				}
			}
		}
		 = 
	}
	return 
}