// Weighted shortest-path search: single-source Dijkstra with path
// reconstruction, and the bidirectional point-to-point variant that meets
// in the middle.

package chickpeas

import (
	
	
)

// WeightFn returns the non-negative cost of traversing a rel (Dijkstra's
// assumption). It receives the step's source node and the RelRef, so it can
// read a stored weight via RelProp(rel.Pos, ...) or compute a derived cost;
// +Inf prunes the rel.
type WeightFn func(from NodeID, rel RelRef) float64

// ShortestPaths is the result of a Dijkstra search: the shortest distance
// to every reached node, with predecessors for path reconstruction. Small
// searches keep map state; a search that settles a large set switches to
// dense id-space arrays mid-run (see densify), so point queries stay
// cheap while component-scale searches avoid per-edge map probes.
type ShortestPaths struct {
	source NodeID
	// Sparse state (dense == false).
	dist map[NodeID]float64
	prev map[NodeID]NodeID
	// Dense state: distances indexed by id (+Inf = unreached), prev
	// indexed by id (prevNone = none), reached in settle-set order.
	dense   bool
	distA   []float64
	prevA   []NodeID
	reached []NodeID
	distMap map[NodeID]float64 // Distances() materialization, lazy
}

// prevNone marks a dense-mode node with no predecessor.
const prevNone = NodeID(^uint32(0))

// Distance is the shortest distance from the source to node; ok is false
// when unreached.
func ( *ShortestPaths) ( NodeID) (float64, bool) {
	if .dense {
		if int() >= len(.distA) || math.IsInf(.distA[], 1) {
			return 0, false
		}
		return .distA[], true
	}
	,  := .dist[]
	return , 
}

// Reached reports whether node was reached from the source.
func ( *ShortestPaths) ( NodeID) bool {
	,  := .Distance()
	return 
}

// Distances is every reached node with its shortest distance (the source
// itself at 0). The returned map is the result's own -- callers must not
// mutate it while still using PathTo.
func ( *ShortestPaths) () map[NodeID]float64 {
	if !.dense {
		return .dist
	}
	if .distMap == nil {
		.distMap = make(map[NodeID]float64, len(.reached))
		for ,  := range .reached {
			.distMap[] = .distA[]
		}
	}
	return .distMap
}

// PathTo is the shortest path from the source to target as a node sequence
// (source first); ok is false when target was unreached.
func ( *ShortestPaths) ( NodeID) ([]NodeID, bool) {
	if !.Reached() {
		return nil, false
	}
	 := []NodeID{}
	for  := ;  != .source; {
		var  NodeID
		if .dense {
			 = .prevA[]
			if  == prevNone {
				return nil, false
			}
		} else {
			var  bool
			,  = .prev[]
			if ! {
				return nil, false
			}
		}
		 = append(, )
		 = 
	}
	for ,  := 0, len()-1;  < ; ,  = +1, -1 {
		[], [] = [], []
	}
	return , true
}

// lookup is the mode-dispatched dist read used by the search loop.
func ( *ShortestPaths) ( NodeID) (float64, bool) {
	if .dense {
		 := .distA[]
		return , !math.IsInf(, 1)
	}
	,  := .dist[]
	return , 
}

// set is the mode-dispatched relax write.
func ( *ShortestPaths) ( NodeID,  float64,  NodeID) {
	if .dense {
		if math.IsInf(.distA[], 1) {
			.reached = append(.reached, )
		}
		.distA[] = 
		.prevA[] = 
		return
	}
	.dist[] = 
	.prev[] = 
}

// densify moves a search that has settled a large set onto dense
// id-space arrays: one O(idspace) initialization buys per-edge relaxes
// with no hashing. Runs at most once per search.
func ( *ShortestPaths) ( int) {
	.distA = make([]float64, )
	.prevA = make([]NodeID, )
	for  := range .distA {
		.distA[] = math.Inf(1)
		.prevA[] = prevNone
	}
	.reached = make([]NodeID, 0, 2*len(.dist))
	for ,  := range .dist {
		.distA[] = 
		.reached = append(.reached, )
	}
	for ,  := range .prev {
		.prevA[] = 
	}
	.dense = true
	.dist, .prev = nil, nil
}

// dijkstraHeap is a min-heap ordered by cost, ties broken by node id (a
// total order, so runs are deterministic).
type dijkstraState struct {
	cost float64
	node NodeID
}

type dijkstraHeap []dijkstraState

// less orders by cost, ties by node id -- a total order for
// deterministic runs.
func ( dijkstraHeap) (,  int) bool {
	if [].cost != [].cost {
		return [].cost < [].cost
	}
	return [].node < [].node
}

// push and pop are hand-rolled sift operations: the container/heap form
// boxes every state into an interface value, allocating once per push in
// the search's hottest loop.
func ( *dijkstraHeap) ( dijkstraState) {
	* = append(*, )
	 := *
	for  := len() - 1;  > 0; {
		 := ( - 1) / 2
		if !.less(, ) {
			break
		}
		[], [] = [], []
		 = 
	}
}

func ( *dijkstraHeap) () dijkstraState {
	 := *
	 := [0]
	 := len() - 1
	[0] = []
	 = [:]
	* = 
	for  := 0; ; {
		 := 2* + 1
		if  >=  {
			break
		}
		 := 
		if  :=  + 1;  <  && .less(, ) {
			 = 
		}
		if !.less(, ) {
			break
		}
		[], [] = [], []
		 = 
	}
	return 
}

// Dijkstra runs weighted single-source shortest paths from source over the
// m-matched rels in dir, reaching the whole component.
func ( *Snapshot) ( NodeID,  Direction,  RelMatch,  WeightFn) *ShortestPaths {
	return .dijkstra(, , , , false, 0)
}

// DijkstraTo is Dijkstra stopping as soon as target's shortest distance is
// known -- the single-pair form; the result still answers every node
// settled before the stop.
func ( *Snapshot) (,  NodeID,  Direction,  RelMatch,  WeightFn) *ShortestPaths {
	return .dijkstra(, , , , true, )
}

// dijkstraDenseAt is the settled-set size at which a search abandons its
// maps for dense id-space arrays -- big enough that point queries never
// pay the O(idspace) initialization, small enough that component-scale
// searches amortize it away.
const dijkstraDenseAt = 4096

func ( *Snapshot) ( NodeID,  Direction,  RelMatch,  WeightFn,
	 bool,  NodeID) *ShortestPaths {
	 := &ShortestPaths{
		source: ,
		dist:   map[NodeID]float64{: 0},
		prev:   map[NodeID]NodeID{},
	}
	 := dijkstraHeap{{cost: 0, node: }}
	for len() > 0 {
		 := .pop()
		if ,  := .lookup(.node);  && .cost >  {
			continue // stale heap entry: a shorter path was found after pushing
		}
		if  && .node ==  {
			break
		}
		for  := range .RelsMatch(.node, , ) {
			 := .cost + (.node, )
			if math.IsInf(, 1) {
				continue // +Inf prunes the rel, per the WeightFn contract
			}
			if ,  := .lookup(.Neighbor); ! ||  <  {
				.set(.Neighbor, , .node)
				.push(dijkstraState{cost: , node: .Neighbor})
			}
		}
		if !.dense && len(.dist) >= dijkstraDenseAt {
			.densify(int(.CSRIDSpace()))
		}
	}
	return 
}

// wspScratch is one bidirectional search's reusable state, pooled so a
// stage running many point-to-point searches (one per candidate pair)
// pays map and heap construction once per pool entry instead of per
// search -- clear keeps the maps' buckets, the heaps keep their backing.
type wspScratch struct {
	distF, distB map[NodeID]float64
	heapF, heapB dijkstraHeap
}

var wspPool = sync.Pool{New: func() any {
	return &wspScratch{distF: map[NodeID]float64{}, distB: map[NodeID]float64{}}
}}

// WeightedShortestPath is the shortest-path cost from source to target via
// bidirectional Dijkstra: it searches from both ends and meets in the
// middle, exploring far fewer nodes than a one-directional search for a
// point-to-point query. ok is false when target is unreachable. The
// backward search follows the reverse of dir, so weight must be symmetric
// (the usual case for an undirected Both traversal). For distances to many
// targets, or the path itself, use Dijkstra.
func ( *Snapshot) (,  NodeID,  Direction,  RelMatch,  WeightFn) (float64, bool) {
	if  ==  {
		return 0, true
	}
	 := .Reverse()
	 := wspPool.Get().(*wspScratch)
	defer wspPool.Put()
	clear(.distF)
	clear(.distB)
	,  := .distF, .distB
	[] = 0
	[] = 0
	 := append(.heapF[:0], dijkstraState{cost: 0, node: })
	 := append(.heapB[:0], dijkstraState{cost: 0, node: })
	defer func() { .heapF, .heapB = [:0], [:0] }()
	// Best meeting cost so far; the frontiers cannot beat topF + topB.
	 := math.Inf(1)

	for len() > 0 || len() > 0 {
		,  := math.Inf(1), math.Inf(1)
		if len() > 0 {
			 = [0].cost
		}
		if len() > 0 {
			 = [0].cost
		}
		if + >=  {
			break
		}
		// Expand whichever frontier is currently cheaper.
		, , ,  := &, , , 
		if  >  {
			, , ,  = &, , , 
		}
		if len(*) == 0 {
			continue
		}
		 := .pop()
		if ,  := [.node];  && .cost >  {
			continue // stale
		}
		// s.node is settled on this side; if the other side reached it too,
		// the two halves form a candidate path.
		if ,  := [.node];  && .cost+ <  {
			 = .cost + 
		}
		for  := range .RelsMatch(.node, , ) {
			 := .cost + (.node, )
			if math.IsInf(, 1) {
				continue // +Inf prunes the rel, per the WeightFn contract
			}
			if ,  := [.Neighbor]; ! ||  <  {
				[.Neighbor] = 
				.push(dijkstraState{cost: , node: .Neighbor})
			}
		}
	}
	if !math.IsInf(, 1) {
		return , true
	}
	return 0, false
}