// First-claim value propagation (PropagateBFS): a depth-bounded BFS from
// seeded nodes where the first relationship, in BFS order, to reach a node
// claims it and sets the value the node carries onward. The claim order --
// FIFO over discovery, with each expansion's eligible rels ordered by a rel
// property and optionally truncated -- is part of the semantics, not a perf
// detail: which rels survive truncation decides which rel claims which
// neighbor. Each seed runs an independent traversal (its own visited set);
// across seeds a node accumulates the sum of its per-seed claimed values
// and its minimum depth. This is the stateful money-flow/taint-trace shape
// (e.g. LDBC FinBench's truncated fund tracing) that path enumeration plus
// aggregation cannot reproduce and that explodes on hubs if attempted.

package chickpeas

import 

// PropagateSeed is one traversal start: the node enters at depth 1 carrying
// Value. The same node may be seeded more than once (one run each).
type PropagateSeed struct {
	Node  NodeID
	Value float64
}

// PropagateResult is one reached node with its accumulated value and
// minimum depth (seeds are depth 1).
type PropagateResult struct {
	Node  NodeID
	Value float64
	Depth uint32
}

// PropagateOpts parameterizes PropagateBFS. The zero value is not useful:
// set RelTypes (empty matches no rels), Direction, MaxDepth, and ValueProp.
type PropagateOpts struct {
	// RelTypes is the fan-out union of relationship types.
	RelTypes []string
	// Direction of expansion from each claimed node.
	Direction Direction
	// MaxDepth bounds the traversal: seeds sit at depth 1 and nodes expand
	// while their depth is below MaxDepth (values below 1 mean seeds only).
	MaxDepth uint32
	// ValueProp is the float rel property a claiming rel carries to the
	// node it claims (absent values read as 0).
	ValueProp string
	// Desc orders each expansion's eligible rels by ValueProp descending
	// instead of ascending. Ties keep adjacency order (stable).
	Desc bool
	// TruncLimit caps each expansion's ordered rels (0 = no cap).
	TruncLimit int
	// MinValue is an exclusive lower bound on a claiming rel's carried
	// value; the default 0 propagates only positive values. Pass -Inf to
	// disable.
	MinValue float64
	// FilterProp, when non-empty, names an integer rel property that must
	// be present and within [FilterMin, FilterMax] for a rel to be
	// eligible at all.
	FilterProp           string
	FilterMin, FilterMax int64
}

// propEntry is one node's accumulation across per-seed runs.
type propEntry struct {
	value float64
	depth uint32
}

// propHop is a queued claim: node claimed at depth carrying value.
type propHop struct {
	node  NodeID
	depth uint32
	value float64
}

// propRel is one eligible fan-out rel during an expansion.
type propRel struct {
	value float64
	nbr   NodeID
}

// PropagateBFS runs first-claim value propagation from seeds under opts.
// Results are sorted by node id ascending; a seed node itself is a result
// (depth 1, its seed value) even when it expands nowhere.
func ( *Snapshot) ( []PropagateSeed,  PropagateOpts) []PropagateResult {
	var  F64Col
	 := false
	if .ValueProp != "" {
		if ,  := .RelCol(.ValueProp);  && .Dtype() == DtypeF64 {
			 = .F64()
			 = true
		}
	}
	var  I64Col
	 := false
	if .FilterProp != "" {
		if ,  := .RelCol(.FilterProp);  && .Dtype() == DtypeI64 {
			 = .I64()
			 = true
		}
	}
	 := .FilterProp != ""

	 := map[NodeID]*propEntry{}
	var  []propHop
	var  []propRel
	for ,  := range  {
		 := map[NodeID]bool{.Node: true}
		 = append([:0], propHop{.Node, 1, .Value})
		for  := 0;  < len(); ++ {
			 := []
			if  := [.node];  != nil {
				.value += .value
				if .depth < .depth {
					.depth = .depth
				}
			} else {
				[.node] = &propEntry{.value, .depth}
			}
			if .depth >= .MaxDepth {
				continue
			}
			 = [:0]
			for  := range .Rels(.node, .Direction, .RelTypes...) {
				if  {
					if ! {
						continue
					}
					,  := .Get(.Pos)
					if ! ||  < .FilterMin ||  > .FilterMax {
						continue
					}
				}
				 := 0.0
				if  {
					, _ = .Get(.Pos)
				}
				 = append(, propRel{, .Neighbor})
			}
			if .Desc {
				sort.SliceStable(, func(,  int) bool { return [].value > [].value })
			} else {
				sort.SliceStable(, func(,  int) bool { return [].value < [].value })
			}
			if .TruncLimit > 0 && len() > .TruncLimit {
				 = [:.TruncLimit]
			}
			for ,  := range  {
				if .value > .MinValue && ![.nbr] {
					[.nbr] = true
					 = append(, propHop{.nbr, .depth + 1, .value})
				}
			}
		}
	}

	 := make([]PropagateResult, 0, len())
	for ,  := range  {
		 = append(, PropagateResult{, .value, .depth})
	}
	sort.Slice(, func(,  int) bool { return [].Node < [].Node })
	return 
}