// Builder removal surface: property removal (node and rel), rel tombstones,
// and detach-delete of nodes. Removals stay cheap at call time -- rels are
// tombstoned rather than compacted so handed-out rel indexes stay stable,
// and detach-delete defers the incident-rel cascade to Finalize (no per-node
// rel index exists, so an eager cascade would be O(m) per call).
//
// Miss-reporting convention, uniform across the family: every method
// reports whether it changed staged state as a bool, and an error is
// reserved for a dangling rel handle -- a rel index (returned by AddRel)
// or (u, v, type) address that is out of range, tombstoned, or dead via a
// detach-deleted endpoint is ErrRelNotFound. Node ids are open-world
// (AddNodeWithID stages arbitrary ids), so an unknown node is a plain
// miss, never an error.

package chickpeas

import (
	

	
)

// removeAllPairs filters every pair staged for id, preserving staging order,
// reporting whether any was removed.
func removeAllPairs[ interface{ () uint32 }]( [],  uint32) ([], bool) {
	 := [:0]
	for ,  := range  {
		if .() !=  {
			 = append(, )
		}
	}
	return , len() != len()
}

// sweepPairs applies removeAllPairs to one key of a typed column map,
// dropping the key entirely when its last pair goes (an empty column must
// not finalize).
func sweepPairs[ interface{ () uint32 }]( map[PropertyKey][],  PropertyKey,  uint32) bool {
	,  := []
	if ! {
		return false
	}
	,  := removeAllPairs(, )
	if len() == 0 {
		delete(, )
	} else {
		[] = 
	}
	return 
}

// removeNodePropPairsByKey sweeps node's staged pairs for key across all
// four typed columns -- a key staged under multiple value types loses every
// staged occurrence, enforcing last-write-wins across types (Finalize's four
// column loops would otherwise resolve a cross-type duplicate by loop order,
// not write order).
func ( *Builder) ( NodeID,  PropertyKey) bool {
	 := sweepPairs(.nodeColI64, , )
	 = sweepPairs(.nodeColF64, , ) || 
	 = sweepPairs(.nodeColBool, , ) || 
	 = sweepPairs(.nodeColStr, , ) || 
	if  {
		.markNodeColDirty()
	}
	return 
}

// RemoveProp deletes node's staged property key, sweeping every staged
// occurrence across all four typed columns (duplicate staged writes and
// cross-type stagings all go -- a partial sweep would resurrect a stale
// value at Finalize). Reports whether anything was removed; false covers
// every kind of miss uniformly (a key never staged anywhere, or staged but
// not on this node -- either way no staged pair existed for (node, key)).
func ( *Builder) ( NodeID,  string) bool {
	,  := .interner.Get()
	if ! {
		return false
	}
	return .removeNodePropPairsByKey(, )
}

// removeNodePropPairs purges every staged property pair of node across all
// keys (the detach-delete property sweep). Only the keys that actually held a
// pair for node are marked dirty; the rest still alias at Finalize, since a
// node the column never covered leaves the rebuilt column unchanged.
func ( *Builder) ( NodeID) {
	 := func( bool,  PropertyKey) {
		if  {
			.markNodeColDirty()
		}
	}
	for  := range .nodeColI64 {
		(sweepPairs(.nodeColI64, , ), )
	}
	for  := range .nodeColF64 {
		(sweepPairs(.nodeColF64, , ), )
	}
	for  := range .nodeColBool {
		(sweepPairs(.nodeColBool, , ), )
	}
	for  := range .nodeColStr {
		(sweepPairs(.nodeColStr, , ), )
	}
}

// RemoveRelProp deletes the staged property key on the first rel matching
// (u, v, relType) -- the addressing dual of SetRelProp. For parallel rels,
// address the specific rel via RemoveRelPropAt. Reports whether anything
// was removed; an unmatched (u, v, relType) address is ErrRelNotFound.
func ( *Builder) (,  NodeID, ,  string) (bool, error) {
	,  := .findRelIndex(, , )
	if ! {
		return false, fmt.Errorf("%w: (%d)-[:%s]->(%d)", ErrRelNotFound, , , )
	}
	return .RemoveRelPropAt(, )
}

// RemoveRelPropAt deletes the staged property key on the rel at relIdx (as
// returned by AddRel), sweeping every staged occurrence across all four
// typed columns. Reports whether anything was removed -- a key with no
// staged pair on this rel is (false, nil), distinguishable from a real
// removal; a removed or out-of-range rel is ErrRelNotFound.
func ( *Builder) ( int,  string) (bool, error) {
	if  < 0 ||  >= len(.rels) || .relRemoved() {
		return false, fmt.Errorf("%w: rel index %d", ErrRelNotFound, )
	}
	,  := .interner.Get()
	if ! {
		return false, nil
	}
	 := uint32()
	 := sweepPairs(.relColI64, , )
	 = sweepPairs(.relColF64, , ) || 
	 = sweepPairs(.relColBool, , ) || 
	 = sweepPairs(.relColStr, , ) || 
	if  {
		.markRelColDirty()
	}
	return , nil
}

// RemoveRel tombstones the rel at relIdx (as returned by AddRel). The
// staging array is never compacted -- swap-removal would invalidate every
// handed-out rel index and the staged rel-prop ids -- so the rel is marked
// removed, degrees adjust immediately, and Finalize compacts in one pass.
// Removing a rel that is out of range, already removed, or dead via a
// detach-deleted endpoint is ErrRelNotFound (no removed bool: a live
// handle always removes, so removal happened exactly when err is nil).
func ( *Builder) ( int) error {
	if  < 0 ||  >= len(.rels) || .relRemoved() {
		return fmt.Errorf("%w: rel index %d", ErrRelNotFound, )
	}
	if .removedRels == nil {
		.removedRels = roaring.New()
	}
	.markRelsDirty()
	.removedRels.Add(uint32())
	 := .rels[]
	.degOut[[0]]--
	.degIn[[1]]--
	.relIndex = nil // lazily rebuilds skipping tombstones
	return nil
}

// RemoveNode detach-deletes node: its labels and staged properties go
// immediately, and every currently staged incident rel (with its rel
// properties) dies at Finalize. Reports whether the node was known.
//
// Ids retire rather than reuse -- nextNodeID never rewinds -- but removal is
// not a permanent tombstone: any later staging touch (AddNodeWithID,
// SetProp, or being an AddRel endpoint) resurrects the id as a fresh
// unlabeled, propertyless node. Rels staged before the removal stay dead
// either way; only rels added after the resurrection survive.
func ( *Builder) ( NodeID) bool {
	if !.knownNodes.Contains() {
		return false
	}
	.knownNodes.Remove()
	if int() < len(.nodeLabels) {
		for ,  := range .nodeLabels[] {
			.markLabelDirty()
		}
		.nodeLabels[] = nil
	}
	// The Finalize cascade rescans every staged rel for incidence, so the rel
	// set is treated as dirty even when the node turns out to be isolated.
	.markRelsDirty()
	.removeNodePropPairs()
	if .removedNodes == nil {
		.removedNodes = map[NodeID]int{}
	}
	// The watermark (staged rel count at removal time) scopes the Finalize
	// cascade: rels below it die, rels added afterwards -- which resurrect
	// the node -- survive.
	.removedNodes[] = len(.rels)
	.relIndex = nil // may reference rels the cascade will kill
	return true
}

// relRemoved reports whether the staged rel at idx is dead -- explicitly
// tombstoned, or staged before a detach-delete of either endpoint.
func ( *Builder) ( int) bool {
	if .removedRels != nil && .removedRels.Contains(uint32()) {
		return true
	}
	if len(.removedNodes) > 0 {
		 := .rels[]
		if ,  := .removedNodes[[0]];  &&  <  {
			return true
		}
		if ,  := .removedNodes[[1]];  &&  <  {
			return true
		}
	}
	return false
}

// hasRemovals reports whether any removal state is pending compaction.
func ( *Builder) () bool {
	return (.removedRels != nil && !.removedRels.IsEmpty()) || len(.removedNodes) > 0
}

// compactRemovals folds pending removals into the staging state in one
// O(m + pairs) pass: dead rels (explicit tombstones plus the detach-delete
// cascade) drop out in staging order, degrees recompute, and staged rel-prop
// pairs remap to the compacted indexes or drop with their rel. Called by
// Finalize before any build phase; a no-op without removals.
func ( *Builder) () {
	if !.hasRemovals() {
		return
	}
	 := len(.rels)
	 := make([]int32, )
	 := 0
	for  := range  {
		if .relRemoved() {
			[] = -1
			continue
		}
		[] = int32()
		.rels[] = .rels[]
		.relTypes[] = .relTypes[]
		++
	}
	.rels = .rels[:]
	.relTypes = .relTypes[:]
	clear(.degOut)
	clear(.degIn)
	for ,  := range .rels {
		.degOut[[0]]++
		.degIn[[1]]++
	}
	remapRelPairs(.relColI64, )
	remapRelPairs(.relColF64, )
	remapRelPairs(.relColBool, )
	remapRelPairs(.relColStr, )
	.removedRels = nil
	.removedNodes = nil
	.relIndex = nil
}

// relPair constrains the four staged pair types for compaction remapping.
type relPair[ any] interface {
	pairID() uint32
	withPairID(id uint32) 
}

// remapRelPairs rewrites staged rel-prop ids through the compaction map,
// dropping pairs on dead rels and keys left empty.
func remapRelPairs[ relPair[]]( map[PropertyKey][],  []int32) {
	for ,  := range  {
		 := [:0]
		for ,  := range  {
			 := [.pairID()]
			if  < 0 {
				continue
			}
			 = append(, .withPairID(uint32()))
		}
		if len() == 0 {
			delete(, )
		} else {
			[] = 
		}
	}
}