// Typed adjacency: a lazily built per-(type, direction) CSR view --
// offsets, neighbors, and original CSR positions restricted to one
// relationship type -- so a single-type traversal reads a contiguous run
// with no per-relationship type test. Snapshot.Match resolves the view
// holder once per compiled stage (one lock there, none per traversal
// call); each direction builds on first traversal via its own Once.
// Per-node relative order matches the primary CSR, so routed results are
// byte-identical to the scan path. Types below the memory floor never
// build the id-space offsets array (it would dwarf their payload); they
// route through a payload-proportional run view instead (typedRuns), with
// the type-tested scan remaining the multi-type / MatchAll path.

package chickpeas

import (
	
	
	
	
)

// typedCSR is one direction's single-type view. nil means "not built"
// (below the floor): callers fall back to the type-tested scan.
type typedCSR struct {
	offsets []uint32
	nbrs    []NodeID
	poss    []uint32
}

// typedRuns is one direction's below-floor single-type view: the type's
// relationships filtered from the primary CSR in original order, with the
// owning node per entry instead of id-space offsets -- memory proportional
// to the type's payload. A node's run resolves through the bucket hint: a
// binary search bounded to the node's 64-id bucket, so the probes land in
// the one or two cache lines the bucket's entries span instead of walking
// ~log2(E) cold lines across the whole owner array. Per-node relative
// order matches the primary CSR, so routed results are byte-identical to
// the scan path.
type typedRuns struct {
	nodes []uint32
	nbrs  []NodeID
	poss  []uint32
	// hint[b] is the index in nodes of the first entry whose owner falls in
	// bucket b (owners b<<runHintShift and up); hint[b+1] closes the bucket.
	// One uint32 per 64 id-space slots -- 1/64th of the full offsets array
	// the typed floor exists to avoid -- built read-only with the view, so
	// parallel kernels share it without a single cross-core write.
	hint []uint32
}

// runHintShift buckets the owner array by node id for the run-range
// lookup: 64 ids per bucket keeps the hint at one uint32 per 64 id-space
// slots while bounding each lookup's search to the entries of 64
// consecutive nodes of a below-floor type -- typically a handful, always
// contiguous. The known alternative is a FULL per-id starts array --
// strictly O(1) runRange at ~64x the memory per consulted direction
// (id-space proportional, the very array the typed floor avoids); since
// these views already build lazily per consulted (type, direction), that
// trade is one constant swap away if a CPU profile ever shows the
// bucketed search hot on a hub-heavy workload.
const runHintShift = 6

// runRange is node's [lo, hi) span in the run view: the hint bounds the
// bucket, a binary search inside it finds the run start.
func ( *typedRuns) ( NodeID) (int, int) {
	 := int() >> runHintShift
	if +1 >= len(.hint) {
		return 0, 0
	}
	,  := int(.hint[]), int(.hint[+1])
	,  := slices.BinarySearch(.nodes[:], uint32())
	 += 
	 := 
	for  <  && .nodes[] == uint32() {
		++
	}
	return , 
}

// typedPair lazily holds both directions' views for one type, plus the
// type's sorted edge-key set and below-floor run views -- representations
// sized by the type's payload instead of the id space.
type typedPair struct {
	g               *Snapshot
	t               RelType
	outOnce         sync.Once
	inOnce          sync.Once
	out, in         *typedCSR
	edgeOnce        sync.Once
	edges           []uint64
	outRunsOnce     sync.Once
	inRunsOnce      sync.Once
	outRuns, inRuns *typedRuns
}

// typedFloor: a type builds its view only when its relationship count is
// at least CSRIDSpace/4, keeping the mandatory offsets array (one uint32
// per id-space slot) proportional to the payload it accelerates.
func ( *Snapshot) ( RelType) bool {
	,  := .typeIndex[]
	if ! {
		return false
	}
	return .Len()*4 >= int(.CSRIDSpace())
}

// view returns the direction's typed CSR, building it on first use; nil
// when the type sits below the floor.
func ( *typedPair) ( bool) *typedCSR {
	if  == nil {
		return nil
	}
	if  {
		.outOnce.Do(func() {
			if .g.typedAboveFloor(.t) {
				.out = buildTypedCSR(.g.outOffsets, .g.outNbrs, .g.outTypes, nil, .t)
			}
		})
		return .out
	}
	.inOnce.Do(func() {
		if .g.typedAboveFloor(.t) {
			// The incoming view bakes the inToOut position mapping in, so
			// property reads index the stored (outgoing) positions directly;
			// an absent mapping keeps raw indexes, mirroring relsYield.
			.in = buildTypedCSR(.g.inOffsets, .g.inNbrs, .g.inTypes, .g.getInToOut(), .t)
		}
	})
	return .in
}

// runs returns the direction's below-floor run view, building it on first
// use; nil when the type has a full typed CSR instead (at or above the
// floor, where view() serves the traversal). Together the two views cover
// every single-type traversal: contiguous per-type CSR above the floor,
// payload-proportional filtered runs below it.
func ( *typedPair) ( bool) *typedRuns {
	if  == nil {
		return nil
	}
	if  {
		.outRunsOnce.Do(func() {
			if !.g.typedAboveFloor(.t) {
				.outRuns = buildTypedRuns(.g.outOffsets, .g.outNbrs, .g.outTypes, nil, .t, .typeCount())
			}
		})
		return .outRuns
	}
	.inRunsOnce.Do(func() {
		if !.g.typedAboveFloor(.t) {
			.inRuns = buildTypedRuns(.g.inOffsets, .g.inNbrs, .g.inTypes, .g.getInToOut(), .t, .typeCount())
		}
	})
	return .inRuns
}

// typeCount is the type's relationship count (0 for an unknown type).
func ( *typedPair) () int {
	if ,  := .g.typeIndex[.t];  {
		return .Len()
	}
	return 0
}

// buildTypedRuns filters one direction's CSR to a single type in one
// linear pass, keeping the owning node per entry, then indexes the owner
// array with the bucket hint. poss carries each kept relationship's
// property-read position, mapped like buildTypedCSR's.
func buildTypedRuns( []uint32,  []NodeID,  []RelType,  []uint32,  RelType,  int) *typedRuns {
	 := &typedRuns{
		nodes: make([]uint32, 0, ),
		nbrs:  make([]NodeID, 0, ),
		poss:  make([]uint32, 0, ),
	}
	 := len() - 1
	if  < 0 {
		 = 0
	}
	for  := 0;  < ; ++ {
		for  := int([]);  < int([+1]); ++ {
			if [] ==  {
				 := uint32()
				if  < len() {
					 = []
				}
				.nodes = append(.nodes, uint32())
				.nbrs = append(.nbrs, [])
				.poss = append(.poss, )
			}
		}
	}
	.hint = make([]uint32, (>>runHintShift)+2)
	 := 0
	for ,  := range .nodes {
		for  <= int()>>runHintShift {
			.hint[] = uint32()
			++
		}
	}
	for ;  < len(.hint); ++ {
		.hint[] = uint32(len(.nodes))
	}
	return 
}

// runScanFloor gates the run view per node: at or below this primary-CSR
// degree the type-tested scan reads the node's whole mixed run in a cache
// line or two, beating the view's lookup; above it the filtered contiguous
// run wins. Callers check the degree before consulting runs().
const runScanFloor = 64

// typedSlotsLen is the dense-id fast path's span: rel-type atoms below it
// resolve through a per-slot atomic slice (two atomic loads, no hashing);
// the rare larger atom falls back to the copy-on-write map. Both paths
// are lock-free on the hit, since the string-typed traversal conveniences
// resolve per call inside kernel loops.
const typedSlotsLen = 4096

// typedPairFor returns the shared lazy holder for one type. Holders are
// created at most once per type (CAS / mutexed copy-insert) and never
// replaced, so a stale read only re-misses.
func ( *Snapshot) ( RelType) *typedPair {
	if  < typedSlotsLen {
		 := .typedSlots.Load()
		if  == nil {
			.typedMu.Lock()
			if  = .typedSlots.Load();  == nil {
				 = &[typedSlotsLen]atomic.Pointer[typedPair]{}
				.typedSlots.Store()
			}
			.typedMu.Unlock()
		}
		if  := [].Load();  != nil {
			return 
		}
		 := &typedPair{g: , t: }
		if ![].CompareAndSwap(nil, ) {
			 = [].Load()
		}
		return 
	}
	if  := .typedAdj.Load();  != nil {
		if ,  := (*)[];  {
			return 
		}
	}
	.typedMu.Lock()
	defer .typedMu.Unlock()
	 := .typedAdj.Load()
	if  != nil {
		if ,  := (*)[];  {
			return 
		}
	}
	 := make(map[RelType]*typedPair, 1)
	if  != nil {
		maps.Copy(, *)
	}
	 := &typedPair{g: , t: }
	[] = 
	.typedAdj.Store(&)
	return 
}

// buildTypedCSR restricts one direction's CSR to a single type in one
// linear pass, preserving per-node relative order. poss carries each kept
// relationship's property-read position: the raw index, or posMap[index]
// when a mapping is supplied (the incoming direction's inToOut).
func buildTypedCSR( []uint32,  []NodeID,  []RelType,  []uint32,  RelType) *typedCSR {
	 := len() - 1
	if  < 0 {
		return &typedCSR{offsets: []uint32{0}}
	}
	 := 0
	for ,  := range  {
		if  ==  {
			++
		}
	}
	 := &typedCSR{
		offsets: make([]uint32, +1),
		nbrs:    make([]NodeID, 0, ),
		poss:    make([]uint32, 0, ),
	}
	for  := 0;  < ; ++ {
		.offsets[] = uint32(len(.nbrs))
		for  := int([]);  < int([+1]); ++ {
			if [] ==  {
				 := uint32()
				if  < len() {
					 = []
				}
				.nbrs = append(.nbrs, [])
				.poss = append(.poss, )
			}
		}
	}
	.offsets[] = uint32(len(.nbrs))
	return 
}