// RCPG serialization for Snapshot: conversion to and from the rcpg
// package's on-disk data model. Lazily built indexes are derived data and
// are not serialized; they rebuild on first use after reading.

package chickpeas

import (
	
	
	
	

	
	
	
	
)

// computeInToOutFromCSR recomputes the incoming -> outgoing CSR position
// map from the adjacency arrays alone (used when a snapshot is
// reconstructed on deserialization). It pairs the k-th u->v rel of a given
// type in the outgoing CSR with the k-th such rel in the incoming CSR; both
// CSRs preserve insertion order within a group, so the pairing matches the
// original rels.
//
// Implementation: stable LSD counting sorts bring both sides' position
// arrays into the identical (src, dst, type) group order -- stability
// keeps each side's within-group insertion order, so the i-th entries of
// the two sorted arrays are the same relationship and zip directly. This
// replaces the former per-group map (one entry plus a slice per distinct
// rel key -- the dominant load-time cost at tens of millions of rels)
// with a handful of flat arrays.
func computeInToOutFromCSR( []uint32,  []NodeID,  []RelType,
	 []uint32,  []NodeID,  []RelType) []uint32 {
	 := len()
	 := make([]uint32, )
	if  == 0 {
		return 
	}
	 := max(len()-1, 0)
	 := ownersOf(, )
	 := ownersOf(, )
	 := RelType(0)
	for ,  := range  {
		 = max(, )
	}

	 := ascending()
	 := ascending()
	 := make([]uint32, )
	 := make([]uint32, max(, int()+1)+1)
	// LSD: least-significant key first, so the LAST pass's key is most
	// significant; both sides end in the same (src, dst, type) order.
	countingPass(, , , int()+1, func( uint32) uint32 { return uint32([]) })
	countingPass(, , , , func( uint32) uint32 { return uint32([]) })
	countingPass(, , , , func( uint32) uint32 { return [] })
	countingPass(, , , int()+1, func( uint32) uint32 { return uint32([]) })
	countingPass(, , , , func( uint32) uint32 { return [] })
	countingPass(, , , , func( uint32) uint32 { return uint32([]) })

	for  := range  {
		[[]] = []
	}
	return 
}

// ownersOf expands a CSR offsets array to a per-position owner-node array.
func ownersOf( []uint32,  int) []uint32 {
	 := make([]uint32, )
	for  := 0; +1 < len(); ++ {
		for  := [];  < [+1]; ++ {
			[] = uint32()
		}
	}
	return 
}

// ascending is the identity position permutation.
func ascending( int) []uint32 {
	 := make([]uint32, )
	for  := range  {
		[] = uint32()
	}
	return 
}

// countingPass stably reorders positions in place by key(p) in [0, nKeys),
// staging through tmp.
func countingPass(, ,  []uint32,  int,  func(uint32) uint32) {
	clear([:+1])
	for ,  := range  {
		[()+1]++
	}
	for  := 1;  <= ; ++ {
		[] += [-1]
	}
	for ,  := range  {
		 := ()
		[[]] = 
		[]++
	}
	copy(, )
}

func columnToData( Column) rcpg.ColumnData {
	switch col := .(type) {
	case denseI64Col:
		return rcpg.DenseI64()
	case denseI64NarrowCol:
		// Serialization stays width-agnostic: materialize the logical
		// values; the reader re-narrows on load.
		 := make(rcpg.DenseI64, .Len())
		for  := range  {
			[] = .at(uint32())
		}
		return 
	case denseI64BitCol:
		 := make(rcpg.DenseI64, .Len())
		for  := range  {
			[] = .at(uint32())
		}
		return 
	case denseF64Col:
		return rcpg.DenseF64()
	case denseBoolCol:
		return rcpg.DenseBool{Bits: .bits.ToPackedLSB(), Len: uint32(.bits.Len())}
	case denseStrCol:
		return rcpg.DenseStr()
	case sparseI64Col:
		 := make(rcpg.SparseI64, len(.ids))
		for ,  := range .ids {
			[] = rcpg.I64Entry{ID: , Val: .vals[]}
		}
		return 
	case sparseI64NarrowCol:
		 := make(rcpg.SparseI64, len(.ids))
		for ,  := range .ids {
			[] = rcpg.I64Entry{ID: , Val: .at()}
		}
		return 
	case rankI64NarrowCol:
		// Like rankI64Col: rank/select is in-memory only, serialized as
		// the equivalent sparse column.
		 := make(rcpg.SparseI64, 0, .n())
		for ,  := range .Entries() {
			,  := .I64()
			 = append(, rcpg.I64Entry{ID: , Val: })
		}
		return 
	case sparseF64Col:
		 := make(rcpg.SparseF64, len(.ids))
		for ,  := range .ids {
			[] = rcpg.F64Entry{ID: , Val: .vals[]}
		}
		return 
	case sparseBoolCol:
		 := make(rcpg.SparseBool, len(.ids))
		for ,  := range .ids {
			[] = rcpg.BoolEntry{ID: , Val: .vals[]}
		}
		return 
	case sparseStrCol:
		 := make(rcpg.SparseStr, len(.ids))
		for ,  := range .ids {
			[] = rcpg.StrEntry{ID: , Val: .vals[]}
		}
		return 
	// Rank/select is an in-memory layout only; it serializes as the
	// equivalent sparse column (matching the Rust codec).
	case rankI64Col:
		 := make(rcpg.SparseI64, 0, len(.vals))
		for ,  := range .Entries() {
			,  := .I64()
			 = append(, rcpg.I64Entry{ID: , Val: })
		}
		return 
	case rankF64Col:
		 := make(rcpg.SparseF64, 0, len(.vals))
		for ,  := range .Entries() {
			,  := .F64()
			 = append(, rcpg.F64Entry{ID: , Val: })
		}
		return 
	case rankBoolCol:
		 := make(rcpg.SparseBool, 0, .vals.Len())
		for ,  := range .Entries() {
			,  := .Bool()
			 = append(, rcpg.BoolEntry{ID: , Val: })
		}
		return 
	case rankStrCol:
		 := make(rcpg.SparseStr, 0, len(.vals))
		for ,  := range .Entries() {
			,  := .StrID()
			 = append(, rcpg.StrEntry{ID: , Val: })
		}
		return 
	}
	return nil
}

func dataToColumn( rcpg.ColumnData) Column {
	switch d := .(type) {
	case rcpg.DenseI64:
		return narrowI64Column()
	case rcpg.DenseF64:
		return denseF64Col()
	case rcpg.DenseBool:
		return denseBoolCol{bits: bitset.FromPackedLSB(.Bits, int(.Len))}
	case rcpg.DenseStr:
		return denseStrCol()
	case rcpg.SparseI64:
		 := sparseI64Col{ids: make([]uint32, len()), vals: make([]int64, len())}
		for ,  := range  {
			.ids[], .vals[] = .ID, .Val
		}
		if ,  := narrowI64Vals(.vals);  {
			return sparseI64NarrowCol{ids: .ids, narrowI64Vec: }
		}
		return 
	case rcpg.SparseF64:
		 := sparseF64Col{ids: make([]uint32, len()), vals: make([]float64, len())}
		for ,  := range  {
			.ids[], .vals[] = .ID, .Val
		}
		return 
	case rcpg.SparseBool:
		 := sparseBoolCol{ids: make([]uint32, len()), vals: make([]bool, len())}
		for ,  := range  {
			.ids[], .vals[] = .ID, .Val
		}
		return 
	case rcpg.SparseStr:
		 := sparseStrCol{ids: make([]uint32, len()), vals: make([]uint32, len())}
		for ,  := range  {
			.ids[], .vals[] = .ID, .Val
		}
		return 
	}
	return nil
}

// ToGraphSection converts this snapshot to the plain on-disk data model.
func ( *Snapshot) () *rcpg.GraphSection {
	return .graphSectionWith(true, true)
}

// graphSectionWith builds the on-disk model, converting only the requested
// column sets so topology-only writes copy no property data. Index and
// column orders are sorted by atom, as the format requires.
func ( *Snapshot) (,  bool) *rcpg.GraphSection {
	 := make([]rcpg.BitmapEntry, 0, len(.labelIndex))
	for ,  := range .labelIndex {
		 = append(, rcpg.BitmapEntry{Atom: .ID(), Bitmap: .Bitmap()})
	}
	sort.Slice(, func(,  int) bool { return [].Atom < [].Atom })

	 := make([]rcpg.BitmapEntry, 0, len(.typeIndex))
	for ,  := range .typeIndex {
		 = append(, rcpg.BitmapEntry{Atom: .ID(), Bitmap: .Bitmap()})
	}
	sort.Slice(, func(,  int) bool { return [].Atom < [].Atom })

	 := func( map[PropertyKey]Column,  bool) []rcpg.Column {
		if ! {
			return nil
		}
		 := make([]rcpg.Column, 0, len())
		for ,  := range  {
			 = append(, rcpg.Column{Key: , Data: columnToData()})
		}
		sort.Slice(, func(,  int) bool { return [].Key < [].Key })
		return 
	}

	 := make([]uint32, len(.outTypes))
	for ,  := range .outTypes {
		[] = .ID()
	}
	 := make([]uint32, len(.inTypes))
	for ,  := range .inTypes {
		[] = .ID()
	}

	// The existence section is emitted only for a genuinely sparse id
	// space: dense graphs (every loader) keep byte-identical files, and
	// absent-means-dense is exactly correct for them on read.
	var  *roaring.Bitmap
	if .existence != nil && uint32(.existence.Len()) < .CSRIDSpace() {
		 = .existence.Bitmap()
	}

	return &rcpg.GraphSection{
		NNodes:      .nNodes,
		NRels:       .nRels,
		OutOffsets:  .outOffsets,
		OutNbrs:     .outNbrs,
		OutTypes:    ,
		InOffsets:   .inOffsets,
		InNbrs:      .inNbrs,
		InTypes:     ,
		LabelIndex:  ,
		TypeIndex:   ,
		NodeColumns: (.columns, ),
		RelColumns:  (.relColumns, ),
		Version:     .version,
		Atoms:       .atoms.Strings(),
		Existence:   ,
	}
}

// FromGraphSection builds a snapshot from the on-disk data model. The
// section's slices and bitmaps are taken over; the caller must not reuse
// them.
func ( *rcpg.GraphSection) *Snapshot {
	 := newSnapshot()
	.nNodes = .NNodes
	.nRels = .NRels
	.outOffsets = .OutOffsets
	.outNbrs = .OutNbrs
	.inOffsets = .InOffsets
	.inNbrs = .InNbrs

	.outTypes = make([]RelType, len(.OutTypes))
	for ,  := range .OutTypes {
		.outTypes[] = RelType()
	}
	.inTypes = make([]RelType, len(.InTypes))
	for ,  := range .InTypes {
		.inTypes[] = RelType()
	}

	if .Existence != nil {
		.existence = nodeset.FromBitmap(.Existence)
	}
	for ,  := range .LabelIndex {
		.labelIndex[Label(.Atom)] = nodeset.FromBitmap(.Bitmap)
	}
	for ,  := range .TypeIndex {
		.typeIndex[RelType(.Atom)] = nodeset.FromBitmap(.Bitmap)
	}
	for ,  := range .NodeColumns {
		.columns[.Key] = dataToColumn(.Data)
	}
	for ,  := range .RelColumns {
		.relColumns[.Key] = dataToColumn(.Data)
	}
	.version = .Version
	.atoms = NewAtoms(.Atoms)

	// The format does not store the incoming -> outgoing position map; it is
	// rebuilt lazily on the first incoming rel-property read (getInToOut), so
	// a loaded-but-unqueried graph never pays its footprint. Record only
	// whether rel columns exist, the authority for whether it is ever needed.
	.hasRelProps = len(.relColumns) > 0
	return 
}

// WriteRCPG serializes this snapshot to RCPG bytes including property
// columns (see rustychickpeas-format's FORMAT.md for the layout).
func ( *Snapshot) ( io.Writer) error {
	return .WriteRCPGWith(, rcpg.DefaultWriteOptions())
}

// WriteRCPGWith serializes with optional sections per opts;
// TopologyOnlyWriteOptions produces a lean traversal-only file (no property
// columns converted or written).
func ( *Snapshot) ( io.Writer,  rcpg.WriteOptions) error {
	return rcpg.WriteWith(.graphSectionWith(.NodeColumns, .RelColumns), , )
}

// ReadRCPG reads a snapshot from RCPG bytes. Lazy indexes rebuild on first
// use.
func ( []byte) (*Snapshot, error) {
	,  := rcpg.Parse()
	if  != nil {
		return nil, 
	}
	return FromGraphSection(), nil
}

// WriteRCPGFile serializes to an RCPG file on disk.
func ( *Snapshot) ( string) error {
	,  := os.Create()
	if  != nil {
		return 
	}
	 := bufio.NewWriter()
	if  := .WriteRCPG();  != nil {
		.Close()
		return 
	}
	if  := .Flush();  != nil {
		.Close()
		return 
	}
	return .Close()
}

// ReadRCPGFile reads a snapshot from an RCPG file on disk.
func ( string) (*Snapshot, error) {
	,  := os.ReadFile()
	if  != nil {
		return nil, 
	}
	return ReadRCPG()
}