// Thaw: reconstruct a mutable Builder from an immutable Snapshot, enabling
// the read-modify-refinalize-swap write loop (thaw an existing snapshot,
// edit it, Finalize a new one, register it in the Manager). Snapshots stay
// immutable throughout; the Manager swap is the commit point.

package chickpeas

import 

// NewBuilderFromSnapshot thaws g back into a Builder whose Finalize
// reproduces g -- a no-edit thaw -> Finalize -> WriteRCPG round trip is
// byte-identical for any snapshot Finalize itself can produce. Atom ids are
// preserved exactly (the interner is seeded from g's atom table), so labels,
// rel types, property keys, and string values stay valid across the thaw.
//
// Two lossy corners are inherited from the snapshot representation itself:
//
//   - Dense columns cannot distinguish "never set" from the zero value, so
//     the thaw stages a pair for every position of a dense column (including
//     atom-0 positions of a dense string column, mirroring the read layer,
//     which reports those as present ""). Since tasks/041 Go only finalizes
//     numeric/bool columns dense at full coverage, so this zero-fill
//     materialization arises for those dtypes only when thawing a legacy or
//     Rust-written file whose dense column was written at partial fill --
//     the missingness was destroyed at that write, and refinalize keeps the
//     zero-filled positions as genuine (staged) values.
//   - Ghost nodes -- isolated, unlabeled, propertyless -- leave no
//     identifiable trace. Known nodes rebuild from labels, rel endpoints,
//     and column positions; ghosts outside that union are lost (their count
//     no longer contributes to NodeCount), and dense-column positions inside
//     it may register ids the original builder never saw.
func ( *Snapshot) *Builder {
	 := int(.CSRIDSpace())
	 := len(.outNbrs)
	 := NewBuilder(max(, 1), max(, 1))
	.interner = newInternerFromAtoms(.atoms)
	if .version != nil {
		 := *.version
		.version = &
	}

	// Labels: invert the per-label bitmaps into per-node lists, ascending
	// atom order (per-node order was never serialized and Finalize only
	// rebuilds the bitmaps, so atom order is canonical).
	 := make([]Label, 0, len(.labelIndex))
	for  := range .labelIndex {
		 = append(, )
	}
	slices.Sort()
	for ,  := range  {
		for  := range .labelIndex[].Iter() {
			.nodeLabels[] = append(.nodeLabels[], )
			.knownNodes.Add()
		}
	}

	// Rels: restage in a linear extension of BOTH CSR directions' per-node
	// orders, so refinalizing reproduces each CSR byte-identically (naive
	// outgoing order can permute parallel rels within an incoming range).
	 := thawRels(, )

	for ,  := range .columns {
		thawNodeColumn(, , )
	}
	for ,  := range .relColumns {
		thawRelColumn(, , , )
	}

	// Seed known nodes from the retained existence set when present:
	// label/rel/prop restaging above cannot see a label-less isolated
	// node, so without this a sparse graph's such nodes would vanish
	// across a thaw round trip.
	if .existence != nil {
		for  := range .existence.Iter() {
			.knownNodes.Add()
		}
	}
	if !.knownNodes.IsEmpty() {
		.nextNodeID = .knownNodes.Maximum() + 1
	}
	// Record the source last: every staging call above runs against a
	// source-less builder, so the restage itself dirties nothing and an
	// unedited thaw finalizes by aliasing g wholesale (cow.go).
	.src = 
	.srcRelToOutCSR = invertPositions()
	return 
}

// invertPositions inverts the outgoing-CSR position -> staged rel index map
// into the staged rel index -> outgoing-CSR position map Finalize needs to
// place rel properties when it aliases the CSR rather than rebuilding it.
func invertPositions( []uint32) []uint32 {
	 := make([]uint32, len())
	for ,  := range  {
		[] = uint32()
	}
	return 
}

// thawRels stages g's rels in an order consistent with both CSR directions,
// returning the outgoing-CSR position -> staged rel index map (for rel
// column restaging). Each rel goes through the builder's staging core
// (addRelTyped) so degrees, known endpoints, ceilings, and any future
// staging invariant stay owned by one function.
func thawRels( *Builder,  *Snapshot) []uint32 {
	 := thawRelOrder()
	 := make([]uint32, len())
	 := ownersFromOffsets(.outOffsets)
	for ,  := range  {
		,  := [], .outNbrs[]
		,  := .addRelTyped(, , .outTypes[])
		thawMust()
		[] = uint32()
	}
	return 
}

// thawMust asserts a staging call that cannot fail during thaw: every id
// and index comes from a consistent snapshot, which already fits the
// builder's ceilings (the builder is pre-sized to the snapshot's id space
// and rel count). A failure here is an engine bug, not caller input.
func thawMust( error) {
	if  != nil {
		panic("chickpeas: thaw staging invariant violated: " + .Error())
	}
}

// thawRelOrder computes a staging order (as outgoing-CSR positions) that is
// a linear extension of both CSRs' per-node orders. Each direction imposes a
// chain per node (its CSR range order); the original insertion order is one
// valid extension, so the constraint graph -- at most two predecessors per
// rel -- is acyclic and a Kahn walk recovers an equivalent order. Rels are
// paired across directions by k-th occurrence of (src, dst, type), the same
// pairing computeInToOutFromCSR uses.
func thawRelOrder( *Snapshot) []uint32 {
	 := len(.outNbrs)
	if  == 0 {
		return nil
	}
	 := .inToOut
	if  == nil {
		 = computeInToOutFromCSR(
			.outOffsets, .outNbrs, .outTypes, .inOffsets, .inNbrs, .inTypes)
	}
	 := make([]uint32, )
	for ,  := range  {
		[] = uint32()
	}
	 := ownersFromOffsets(.outOffsets)
	 := ownersFromOffsets(.inOffsets)

	// indeg[p]: one predecessor per chain p is not the head of.
	 := make([]uint8, )
	for  := range  {
		if uint32() != .outOffsets[[]] {
			[]++
		}
		if  := [];  != .inOffsets[[]] {
			[]++
		}
	}
	 := make([]uint32, 0, )
	for  := range  {
		if [] == 0 {
			 = append(, uint32())
		}
	}
	 := make([]uint32, 0, )
	 := func( uint32) {
		[]--
		if [] == 0 {
			 = append(, )
		}
	}
	for  := 0;  < len(); ++ {
		 := []
		 = append(, )
		if +1 < .outOffsets[[]+1] {
			( + 1)
		}
		if  := []; +1 < .inOffsets[[]+1] {
			([+1])
		}
	}
	// A consistent snapshot always yields a complete order; on inconsistent
	// CSRs (a hand-built section whose directions disagree) fall back to
	// outgoing order for the remainder -- the outgoing CSR still round-trips.
	if len() <  {
		 := make([]bool, )
		for ,  := range  {
			[] = true
		}
		for  := range  {
			if ![] {
				 = append(, uint32())
			}
		}
	}
	return 
}

// ownersFromOffsets expands a CSR offset array into the owning node per
// position.
func ownersFromOffsets( []uint32) []NodeID {
	 := len() - 1
	if  < 0 {
		return nil
	}
	 := make([]NodeID, [])
	for  := range  {
		for  := [];  < [+1]; ++ {
			[] = NodeID()
		}
	}
	return 
}

// thawNodeColumn restages one node column as builder pairs (Entries yields
// every position of a dense column, present positions of sparse and
// rank/select layouts), installed through the builder's bulk staging entry
// so each position registers as a known node under the builder's own
// invariants.
func thawNodeColumn( *Builder,  PropertyKey,  Column) {
	switch .Dtype() {
	case DtypeI64:
		 := make([]i64Pair, 0, .Len())
		for ,  := range .Entries() {
			,  := .I64()
			 = append(, i64Pair{id: , val: })
		}
		thawMust(setNodeColumnPairs(, .nodeColI64, , ))
	case DtypeF64:
		 := make([]f64Pair, 0, .Len())
		for ,  := range .Entries() {
			,  := .F64()
			 = append(, f64Pair{id: , val: })
		}
		thawMust(setNodeColumnPairs(, .nodeColF64, , ))
	case DtypeBool:
		 := make([]boolPair, 0, .Len())
		for ,  := range .Entries() {
			,  := .Bool()
			 = append(, boolPair{id: , val: })
		}
		thawMust(setNodeColumnPairs(, .nodeColBool, , ))
	case DtypeStr:
		 := make([]strPair, 0, .Len())
		for ,  := range .Entries() {
			,  := .StrID()
			 = append(, strPair{id: , val: })
		}
		thawMust(setNodeColumnPairs(, .nodeColStr, , ))
	}
}

// thawRelColumn restages one rel column, remapping stored outgoing-CSR
// positions to staged rel indexes (Finalize maps them back), installed
// through the builder's bulk staging entry so every id is checked as a
// live staged rel index.
func thawRelColumn( *Builder,  PropertyKey,  Column,  []uint32) {
	switch .Dtype() {
	case DtypeI64:
		 := make([]i64Pair, 0, .Len())
		for ,  := range .Entries() {
			,  := .I64()
			 = append(, i64Pair{id: [], val: })
		}
		thawMust(setRelColumnPairs(, .relColI64, , ))
	case DtypeF64:
		 := make([]f64Pair, 0, .Len())
		for ,  := range .Entries() {
			,  := .F64()
			 = append(, f64Pair{id: [], val: })
		}
		thawMust(setRelColumnPairs(, .relColF64, , ))
	case DtypeBool:
		 := make([]boolPair, 0, .Len())
		for ,  := range .Entries() {
			,  := .Bool()
			 = append(, boolPair{id: [], val: })
		}
		thawMust(setRelColumnPairs(, .relColBool, , ))
	case DtypeStr:
		 := make([]strPair, 0, .Len())
		for ,  := range .Entries() {
			,  := .StrID()
			 = append(, strPair{id: [], val: })
		}
		thawMust(setRelColumnPairs(, .relColStr, , ))
	}
}