package chickpeas

import 

// Atoms is the immutable interned-string table of a snapshot: id -> string
// with an O(1) reverse index. Atom 0 is always the empty string (the RCPG
// convention: labels, rel types, property keys, and string property values
// are all atom ids, and dense string columns encode missing as atom 0).
type Atoms struct {
	strings []string
	index   map[string]uint32
}

// NewAtoms builds the table from an id-ordered string slice. When the slice
// contains duplicates, the smallest id wins reverse lookups.
func ( []string) *Atoms {
	 := make(map[string]uint32, len())
	for ,  := range  {
		if ,  := []; ! {
			[] = uint32()
		}
	}
	return &Atoms{strings: , index: }
}

// Len is the number of atoms.
func ( *Atoms) () int {
	return len(.strings)
}

// Resolve returns the string for an atom id; ok is false when out of range.
func ( *Atoms) ( uint32) (string, bool) {
	if int() >= len(.strings) {
		return "", false
	}
	return .strings[], true
}

// ID returns the atom id for a string; ok is false when never interned.
func ( *Atoms) ( string) (uint32, bool) {
	,  := .index[]
	return , 
}

// Strings exposes the id-ordered table (for serialization). Callers must
// not mutate it.
func ( *Atoms) () []string {
	return .strings
}

// Interner is the build-side, thread-safe string interner (the Rust side
// uses lasso). The empty string is pre-interned as atom 0, upholding the
// RCPG convention from the start.
type Interner struct {
	mu      sync.RWMutex
	strings []string
	index   map[string]uint32
}

// NewInterner returns an interner holding only atom 0 = "".
func () *Interner {
	return &Interner{
		strings: []string{""},
		index:   map[string]uint32{"": 0},
	}
}

// newInternerFromAtoms seeds a build-side interner with a snapshot's
// id-ordered atom table, preserving every atom id exactly -- the keystone of
// thawing a snapshot back into a builder. Duplicate strings keep the
// smallest id for lookups, matching NewAtoms.
func newInternerFromAtoms( *Atoms) *Interner {
	 := make([]string, len(.strings))
	copy(, .strings)
	if len() == 0 {
		 = []string{""}
	}
	 := make(map[string]uint32, len())
	for ,  := range  {
		if ,  := []; ! {
			[] = uint32()
		}
	}
	return &Interner{strings: , index: }
}

// GetOrIntern returns s's atom id, interning it if new.
func ( *Interner) ( string) uint32 {
	.mu.RLock()
	,  := .index[]
	.mu.RUnlock()
	if  {
		return 
	}
	.mu.Lock()
	defer .mu.Unlock()
	if ,  := .index[];  {
		return 
	}
	 = uint32(len(.strings))
	.strings = append(.strings, )
	.index[] = 
	return 
}

// Get returns s's atom id without interning; ok is false when unknown.
func ( *Interner) ( string) (uint32, bool) {
	.mu.RLock()
	defer .mu.RUnlock()
	,  := .index[]
	return , 
}

// Resolve returns the string for an atom id; ok is false when out of range.
func ( *Interner) ( uint32) (string, bool) {
	.mu.RLock()
	defer .mu.RUnlock()
	if int() >= len(.strings) {
		return "", false
	}
	return .strings[], true
}

// Len is the number of interned strings (including atom 0).
func ( *Interner) () int {
	.mu.RLock()
	defer .mu.RUnlock()
	return len(.strings)
}

// Atoms snapshots the interner into an immutable table.
func ( *Interner) () *Atoms {
	.mu.RLock()
	defer .mu.RUnlock()
	 := make([]string, len(.strings))
	copy(, .strings)
	return NewAtoms()
}