// Builder: the mutable staging area for constructing graphs. Collect nodes,
// rels, and properties, then Finalize into an immutable Snapshot. Node and
// rel ceilings are u32 (the CSR indexes rels by u32 position).
//
// The bulk-loader surfaces of the Rust builder (CSV/Parquet dedup
// machinery) are out of scope -- RCPG files are the interchange format.

package chickpeas

import (
	
	

	
)

// ErrCapacity reports exceeding the u32 node/rel ceiling.
var ErrCapacity = errors.New("capacity exceeded")

// ErrRelNotFound reports a rel-property set on a (u, v, type) that was
// never added.
var ErrRelNotFound = errors.New("relationship not found")

// ErrBadValue reports a property value of an unsupported type.
var ErrBadValue = errors.New("unsupported property value type")

type i64Pair struct {
	id  uint32
	val int64
}
type f64Pair struct {
	id  uint32
	val float64
}
type boolPair struct {
	id  uint32
	val bool
}
type strPair struct {
	id  uint32
	val uint32
}

// Builder stages a graph for finalization.
type Builder struct {
	degOut, degIn []uint32
	rels          [][2]NodeID
	nodeLabels    [][]Label
	relTypes      []RelType
	version       *string

	nodeColI64  map[PropertyKey][]i64Pair
	nodeColF64  map[PropertyKey][]f64Pair
	nodeColBool map[PropertyKey][]boolPair
	nodeColStr  map[PropertyKey][]strPair

	// Rel properties stage by rel index in rels; Finalize remaps them to
	// outgoing-CSR positions.
	relColI64  map[PropertyKey][]i64Pair
	relColF64  map[PropertyKey][]f64Pair
	relColBool map[PropertyKey][]boolPair
	relColStr  map[PropertyKey][]strPair

	interner   *Interner
	nextNodeID NodeID
	knownNodes *roaring.Bitmap

	// relIndex is the lazy (u, v, type) -> first rel index map behind
	// SetRelProp, built on first use and maintained by AddRel so setting
	// one property per rel is O(m + p), not O(m^2).
	relIndex map[[3]uint32]int

	// removedRels tombstones rel indexes removed by RemoveRel; rels is never
	// compacted before Finalize so handed-out indexes stay stable. Nil until
	// the first removal.
	removedRels *roaring.Bitmap
	// removedNodes maps a detach-deleted node to the staged rel count at
	// removal time (its watermark): staged rels below it die in the Finalize
	// cascade, rels added afterwards -- which resurrect the node -- survive.
	// Nil until the first removal.
	removedNodes map[NodeID]int

	// src is the snapshot this builder was thawed from (nil when it was built
	// from scratch), and the dirty sets record which of its components have
	// diverged since. Finalize shares the rest with the successor rather than
	// rebuilding them; see cow.go. srcRelToOutCSR maps a staged rel index to
	// its position in src's outgoing CSR, and survives exactly as long as the
	// rel set stays clean.
	src            *Snapshot
	srcRelToOutCSR []uint32
	dirtyNodeCols  map[PropertyKey]struct{}
	dirtyRelCols   map[PropertyKey]struct{}
	dirtyLabels    map[Label]struct{}
	relsDirty      bool
}

// NewBuilder returns a builder with capacity hints (0 = the 2^20 default);
// the builder auto-grows past them.
func (,  int) *Builder {
	const  = 1 << 20
	if  <= 0 {
		 = 
	}
	if  <= 0 {
		 = 
	}
	return &Builder{
		degOut:      make([]uint32, ),
		degIn:       make([]uint32, ),
		rels:        make([][2]NodeID, 0, ),
		nodeLabels:  make([][]Label, ),
		relTypes:    make([]RelType, 0, ),
		nodeColI64:  map[PropertyKey][]i64Pair{},
		nodeColF64:  map[PropertyKey][]f64Pair{},
		nodeColBool: map[PropertyKey][]boolPair{},
		nodeColStr:  map[PropertyKey][]strPair{},
		relColI64:   map[PropertyKey][]i64Pair{},
		relColF64:   map[PropertyKey][]f64Pair{},
		relColBool:  map[PropertyKey][]boolPair{},
		relColStr:   map[PropertyKey][]strPair{},
		interner:    NewInterner(),
		knownNodes:  roaring.New(),
	}
}

// SetVersion sets the snapshot-level version string.
func ( *Builder) ( string) {
	.version = &
}

// ensureCapacity grows the per-node arrays to cover id.
func ( *Builder) ( NodeID) error {
	if int() < len(.degOut) {
		return nil
	}
	const  = int(^uint32(0))
	 := min((int()+1)*2, )
	if  <= int() {
		return fmt.Errorf("%w: maximum node limit (2^32 - 1)", ErrCapacity)
	}
	 := func( []uint32) []uint32 {
		 := make([]uint32, )
		copy(, )
		return 
	}
	.degOut = (.degOut)
	.degIn = (.degIn)
	 := make([][]Label, )
	copy(, .nodeLabels)
	.nodeLabels = 
	return nil
}

// AddNode adds a node with an auto-generated sequential id.
func ( *Builder) ( ...string) (NodeID, error) {
	 := .nextNodeID
	if  == ^uint32(0) {
		return 0, fmt.Errorf("%w: node id counter reached u32 max", ErrCapacity)
	}
	.nextNodeID =  + 1
	return .addNodeAt(, )
}

// AddNodeWithID adds (or re-labels) the node with the given id; callers map
// their own identifiers onto the u32 space.
func ( *Builder) ( NodeID,  ...string) (NodeID, error) {
	if  >= .nextNodeID {
		.nextNodeID =  + 1
	}
	return .addNodeAt(, )
}

func ( *Builder) ( NodeID,  []string) (NodeID, error) {
	if  := .ensureCapacity();  != nil {
		return 0, 
	}
	for ,  := range  {
		 := Label(.interner.GetOrIntern())
		.nodeLabels[] = append(.nodeLabels[], )
		.markLabelDirty()
	}
	.knownNodes.Add()
	return , nil
}

// AddRel adds a relationship from u to v, returning its rel index (usable
// with SetRelPropAt). Endpoints are registered as known nodes.
func ( *Builder) (,  NodeID,  string) (int, error) {
	return .addRelTyped(, , RelType(.interner.GetOrIntern()))
}

// addRelTyped is the rel-staging core behind AddRel and the thaw restage
// (which arrives with pre-interned type atoms): every rel-staging
// invariant -- the capacity and count ceilings, degrees, known endpoints,
// and lazy relIndex maintenance -- lives here and nowhere else.
func ( *Builder) (,  NodeID,  RelType) (int, error) {
	if  := .ensureCapacity(max(, ));  != nil {
		return 0, 
	}
	if len(.rels) >= int(^uint32(0)) {
		return 0, fmt.Errorf("%w: maximum rel limit (2^32 - 1)", ErrCapacity)
	}
	.markRelsDirty()
	.degOut[]++
	.degIn[]++
	 := len(.rels)
	.rels = append(.rels, [2]NodeID{, })
	.relTypes = append(.relTypes, )
	.knownNodes.Add()
	.knownNodes.Add()
	// Keep the lazy lookup map (if built) consistent; first-match wins for
	// a duplicate (u, v, type).
	if .relIndex != nil {
		 := [3]uint32{, , .ID()}
		if ,  := .relIndex[]; ! {
			.relIndex[] = 
		}
	}
	return , nil
}

// setNodeColumnPairs installs pre-typed staged pairs for one node column
// key wholesale (the thaw restage path), enforcing the same invariants
// SetPropByKey maintains per pair: capacity covers every id and each id
// registers as a known node. An empty slice stages nothing (an empty
// column must not finalize).
func setNodeColumnPairs[ interface{ () uint32 }]( *Builder,  map[PropertyKey][],  PropertyKey,  []) error {
	if len() == 0 {
		return nil
	}
	for ,  := range  {
		if  := .ensureCapacity(.());  != nil {
			return 
		}
		.knownNodes.Add(.())
	}
	[] = 
	return nil
}

// setRelColumnPairs installs pre-typed staged pairs for one rel column key
// wholesale (the thaw restage path), enforcing SetRelPropAt's addressing
// invariant: every pair id must be a live staged rel index.
func setRelColumnPairs[ interface{ () uint32 }]( *Builder,  map[PropertyKey][],  PropertyKey,  []) error {
	if len() == 0 {
		return nil
	}
	for ,  := range  {
		 := int(.())
		if  >= len(.rels) || .relRemoved() {
			return fmt.Errorf("%w: rel index %d", ErrRelNotFound, )
		}
	}
	[] = 
	return nil
}

// stageValue resolves a convenience value, interning strings.
func ( *Builder) ( any) (Value, error) {
	switch v := .(type) {
	case Value:
		return , nil
	case string:
		return StrValue(.interner.GetOrIntern()), nil
	case int64:
		return I64Value(), nil
	case int:
		return I64Value(int64()), nil
	case int32:
		return I64Value(int64()), nil
	case float64:
		return F64Value(), nil
	case bool:
		return BoolValue(), nil
	}
	return Value{}, fmt.Errorf("%w: %T", ErrBadValue, )
}

// InternPropertyKey interns a property-key name (or any string) and returns
// its atom, for reuse with SetPropByKey across many rows.
func ( *Builder) ( string) PropertyKey {
	return .interner.GetOrIntern()
}

// SetProp stages a node property of any supported type (string, int,
// int32, int64, float64, bool, or Value), auto-typed into the matching
// column. The key interns before the value, matching the Rust typed
// setters' atom order.
//
// A key should keep one value type graph-wide: the snapshot stores one
// column (one dtype) per key, so a key staged under several types across
// different nodes keeps only one type's column at Finalize -- the other
// types' pairs are discarded, reported by the snapshot's
// DroppedCrossTypedStagings (zero on a well-typed graph). Restaging one
// node's key under a new type is fine via UpdateProp, which sweeps the
// node's old stagings of every type first.
func ( *Builder) ( NodeID,  string,  any) error {
	 := .interner.GetOrIntern()
	return .SetPropByKey(, , )
}

// SetPropByKey is SetProp with a pre-interned key (see InternPropertyKey).
func ( *Builder) ( NodeID,  PropertyKey,  any) error {
	,  := .stageValue()
	if  != nil {
		return 
	}
	if  := .ensureCapacity();  != nil {
		return 
	}
	.markNodeColDirty()
	switch .Kind() {
	case KindStr:
		,  := .StrID()
		.nodeColStr[] = append(.nodeColStr[], strPair{id: , val: })
	case KindI64:
		,  := .I64()
		.nodeColI64[] = append(.nodeColI64[], i64Pair{id: , val: })
	case KindF64:
		,  := .F64()
		.nodeColF64[] = append(.nodeColF64[], f64Pair{id: , val: })
	case KindBool:
		,  := .Bool()
		.nodeColBool[] = append(.nodeColBool[], boolPair{id: , val: })
	}
	.knownNodes.Add()
	return nil
}

// UpdateProp replaces node's staged value for key rather than staging a
// duplicate write, sweeping every prior staged occurrence across all four
// typed columns so the newest write wins even when it changes the value's
// type (Finalize's per-type column loops would otherwise resolve a
// cross-type duplicate by loop order, not write order).
func ( *Builder) ( NodeID,  string,  any) error {
	 := .interner.GetOrIntern()
	,  := .stageValue()
	if  != nil {
		return 
	}
	.removeNodePropPairsByKey(, )
	return .SetPropByKey(, , )
}

func ( i64Pair) () uint32  { return .id }
func ( f64Pair) () uint32  { return .id }
func ( boolPair) () uint32 { return .id }
func ( strPair) () uint32  { return .id }

func ( i64Pair) ( uint32) i64Pair   { .id = ; return  }
func ( f64Pair) ( uint32) f64Pair   { .id = ; return  }
func ( boolPair) ( uint32) boolPair { .id = ; return  }
func ( strPair) ( uint32) strPair   { .id = ; return  }

// findRelIndex resolves (u, v, relType) to the FIRST matching rel index,
// building the lazy lookup map on first use.
func ( *Builder) (,  NodeID,  string) (int, bool) {
	,  := .interner.Get()
	if ! {
		return 0, false
	}
	if .relIndex == nil {
		.relIndex = make(map[[3]uint32]int, len(.rels))
		for ,  := range .rels {
			if .relRemoved() {
				continue
			}
			 := [3]uint32{[0], [1], .relTypes[].ID()}
			if ,  := .relIndex[]; ! {
				.relIndex[] = 
			}
		}
	}
	,  := .relIndex[[3]uint32{, , }]
	return , 
}

// SetRelProp stages a property on the first rel matching (u, v, relType).
// For parallel rels (same endpoints and type), address the specific rel by
// the index AddRel returned via SetRelPropAt.
func ( *Builder) (,  NodeID, ,  string,  any) error {
	,  := .findRelIndex(, , )
	if ! {
		return fmt.Errorf("%w: (%d)-[:%s]->(%d)", ErrRelNotFound, , , )
	}
	return .SetRelPropAt(, , )
}

// SetRelPropAt stages a property on the rel at the given index (as returned
// by AddRel).
func ( *Builder) ( int,  string,  any) error {
	if  < 0 ||  >= len(.rels) {
		return fmt.Errorf("%w: rel index %d out of range (%d rels)", ErrRelNotFound, , len(.rels))
	}
	if .relRemoved() {
		return fmt.Errorf("%w: rel index %d removed", ErrRelNotFound, )
	}
	 := .interner.GetOrIntern()
	,  := .stageValue()
	if  != nil {
		return 
	}
	.markRelColDirty()
	 := uint32()
	switch .Kind() {
	case KindStr:
		,  := .StrID()
		.relColStr[] = append(.relColStr[], strPair{id: , val: })
	case KindI64:
		,  := .I64()
		.relColI64[] = append(.relColI64[], i64Pair{id: , val: })
	case KindF64:
		,  := .F64()
		.relColF64[] = append(.relColF64[], f64Pair{id: , val: })
	case KindBool:
		,  := .Bool()
		.relColBool[] = append(.relColBool[], boolPair{id: , val: })
	}
	return nil
}