// Delta-aware refinalize: a Builder thawed from a Snapshot tracks which
// components diverge from that source, and Finalize shares -- aliases -- the
// clean ones into the successor instead of rebuilding them. A one-property
// edit no longer costs O(n + m).
//
// The safety argument is the snapshot contract itself: snapshots are strictly
// immutable and the Manager swap is the commit point, so a successor holding
// its source's arrays can never observe a mutation, and Go's GC owns the
// shared backings. Nothing here touches the read path -- traversal still
// scans flat CSR arrays, with no delta overlay or merge-on-read.
//
// Sharing granularity is the component: each CSR direction, each property
// column, each label bitmap, the atom table, and the lazily built indexes
// keyed on them.

package chickpeas

import 

// markNodeColDirty records that key's node column diverges from the source
// snapshot, so Finalize rebuilds it from staged pairs rather than aliasing.
// A no-op on a builder with no source, where nothing can be shared anyway.
func ( *Builder) ( PropertyKey) {
	if .src == nil {
		return
	}
	if .dirtyNodeCols == nil {
		.dirtyNodeCols = map[PropertyKey]struct{}{}
	}
	.dirtyNodeCols[] = struct{}{}
}

// markRelColDirty records that key's rel column diverges from the source.
func ( *Builder) ( PropertyKey) {
	if .src == nil {
		return
	}
	if .dirtyRelCols == nil {
		.dirtyRelCols = map[PropertyKey]struct{}{}
	}
	.dirtyRelCols[] = struct{}{}
}

// markLabelDirty records that label's node membership diverges from the
// source, so its bitmap rebuilds.
func ( *Builder) ( Label) {
	if .src == nil {
		return
	}
	if .dirtyLabels == nil {
		.dirtyLabels = map[Label]struct{}{}
	}
	.dirtyLabels[] = struct{}{}
}

// markRelsDirty records a rel add or removal. Every rel-derived component --
// both CSR directions, inToOut, the type index, every rel column (which is
// keyed by CSR position), and the forest-root caches -- rebuilds, and the
// source's staging-index -> CSR-position map is dropped with them.
func ( *Builder) () {
	if .src == nil {
		return
	}
	.relsDirty = true
	.srcRelToOutCSR = nil
}

// aliasPlan is Finalize's one-shot decision of which source components the
// successor may share, plus a record of what actually aliased so the lazy
// caches keyed on those components can carry forward. A builder with no
// source gets a plan that aliases nothing.
type aliasPlan struct {
	b   *Builder
	src *Snapshot

	// csr: both CSR directions, inToOut, and the type index alias.
	csr bool
	// idSpace: the CSR id space is unchanged, so node-column spans -- and
	// with them the dense/rank/sparse layout selection -- match the source.
	idSpace bool
	// labels: no label bitmap diverged, so the whole label index aliases and
	// the O(n) label scan is skipped outright.
	labels bool

	aliasedNodeCols map[PropertyKey]struct{}
	aliasedRelCols  map[PropertyKey]struct{}
	aliasedLabels   map[Label]struct{}
	// allLabels records that finalizeLabels aliased the label index wholesale.
	allLabels bool
}

// newAliasPlan resolves the builder's dirty state against the shape Finalize
// is about to produce: n id slots and m rels.
func ( *Builder) (,  int) *aliasPlan {
	 := &aliasPlan{
		b:               ,
		src:             .src,
		aliasedNodeCols: map[PropertyKey]struct{}{},
		aliasedRelCols:  map[PropertyKey]struct{}{},
		aliasedLabels:   map[Label]struct{}{},
	}
	if .src == nil {
		return 
	}
	.idSpace =  == int(.src.CSRIDSpace())
	// The offset arrays span the id space and the neighbor arrays span the
	// rel count, so a clean rel set is not enough -- a node added past the
	// old maximum (or a retired maximum) rewrites both offset arrays.
	.csr = !.relsDirty && .idSpace &&  == len(.src.outNbrs) && len(.srcRelToOutCSR) == 
	.labels = len(.dirtyLabels) == 0
	return 
}

// nodeColumn returns the source column to share for key, when the column is
// untouched and its span is unchanged. Sharing also carries the column
// bit-identically, which sidesteps the dense-column "never set vs zero"
// conflation that a rebuild through thaw-staged pairs re-applies.
func ( *aliasPlan) ( PropertyKey) (Column, bool) {
	if .src == nil || !.idSpace {
		return nil, false
	}
	if ,  := .b.dirtyNodeCols[];  {
		return nil, false
	}
	,  := .src.columns[]
	if ! {
		return nil, false
	}
	.aliasedNodeCols[] = struct{}{}
	return , true
}

// relColumn returns the source rel column to share for key. Rel columns are
// stored by outgoing-CSR position, so they may only alias alongside the CSR.
func ( *aliasPlan) ( PropertyKey) (Column, bool) {
	if .src == nil || !.csr {
		return nil, false
	}
	if ,  := .b.dirtyRelCols[];  {
		return nil, false
	}
	,  := .src.relColumns[]
	if ! {
		return nil, false
	}
	.aliasedRelCols[] = struct{}{}
	return , true
}

// labelClean reports whether label's membership is untouched and the source
// holds a bitmap for it.
func ( *aliasPlan) ( Label) bool {
	if .src == nil {
		return false
	}
	if ,  := .b.dirtyLabels[];  {
		return false
	}
	,  := .src.labelIndex[]
	return 
}

// aliasCSR shares both CSR directions and the type index (a pure function of
// the outgoing types) with the source. inToOut follows when the successor has
// rel properties to read through it; when the source had none it was never
// built, and the successor's first rel column pays for it once.
func ( *aliasPlan) ( *Snapshot,  bool) {
	 := .src
	.outOffsets, .outNbrs, .outTypes = .outOffsets, .outNbrs, .outTypes
	.inOffsets, .inNbrs, .inTypes = .inOffsets, .inNbrs, .inTypes
	maps.Copy(.typeIndex, .typeIndex)
	.hasRelProps = 
	if ! {
		return
	}
	// The successor reads through the SAME CSR positions, so it shares the
	// source's position map rather than deriving an identical copy -- forcing
	// the source's lazy build once and completing g's Once with the shared
	// slice, so getInToOut returns it without re-deriving. (A rebuilt CSR
	// takes a different path and never reaches aliasCSR.)
	 := .getInToOut()
	.inToOutOnce.Do(func() { .inToOut =  })
}

// aliasAtoms shares the source atom table when nothing new was interned. The
// interner is seeded from the source and only ever appends, so equal lengths
// mean equal tables -- and skipping the copy matters at LDBC scale, where the
// table holds millions of strings.
func ( *aliasPlan) ( *Builder) *Atoms {
	if .src != nil && .interner.Len() == .src.atoms.Len() {
		return .src.atoms
	}
	return .interner.Atoms()
}

// carryLazyCaches hands the source's built lazy indexes to the successor for
// every entry whose underlying components aliased. propIndex, full-text, geo,
// forest-root, and sparse-column position indexes are expensive builds that
// would otherwise die with the source snapshot at the Manager swap. Entries
// touching a rebuilt component are dropped and rebuild on demand.
//
// The cached values are immutable once built and already shared across
// concurrent queries, so handing out the same pointers is safe; only the
// source's cache maps need their locks while they are read.
func ( *Snapshot) ( *aliasPlan) {
	 := .src
	if  == nil {
		return
	}
	 := func( Label) bool {
		if .allLabels {
			,  := .labelIndex[]
			return 
		}
		,  := .aliasedLabels[]
		return 
	}
	 := func( PropertyKey) bool {
		,  := .aliasedNodeCols[]
		return 
	}

	.propIndexMu.Lock()
	for ,  := range .propIndex {
		// The label-free index (anyLabel) is a pure function of the column.
		if (.key) && (.label == anyLabel || (.label)) {
			.propIndex[] = 
		}
	}
	.propIndexMu.Unlock()

	.fulltextMu.Lock()
	for ,  := range .fulltextIndex {
		if (.key) && (.label) {
			.fulltextIndex[] = 
		}
	}
	.fulltextMu.Unlock()

	.geoMu.Lock()
	for ,  := range .geoIndex {
		if (.latKey) && (.lonKey) && (.label) {
			.geoIndex[] = 
		}
	}
	.geoMu.Unlock()

	if .csr {
		.rootsMu.Lock()
		maps.Copy(.rootsViaIndex, .rootsViaIndex)
		.rootsMu.Unlock()
	}

	.colPosMu.Lock()
	for ,  := range .colPosIndex {
		if () {
			.colPosIndex[] = 
		}
	}
	.colPosMu.Unlock()

	.relColPosMu.Lock()
	for ,  := range .relColPosIndex {
		if ,  := .aliasedRelCols[];  {
			.relColPosIndex[] = 
		}
	}
	.relColPosMu.Unlock()
}