package nigiri import ( "fmt" "maps" "reflect" "sort" "strings" "sync" ) // Validatable interface for entities that can validate themselves type Validatable interface { Validate() error } // IndexBuilder function type for building custom indices type IndexBuilder[T any] func(allItems map[int]*T) any // BaseStore provides generic storage with index management and constraints type BaseStore[T any] struct { items map[int]*T maxID int mu sync.RWMutex itemType reflect.Type indices map[string]any indexBuilders map[string]IndexBuilder[T] schema *SchemaInfo uniqueIndices map[string]map[any]int // field -> value -> id } // NewBaseStore creates a new base store for type T with schema parsing func NewBaseStore[T any]() *BaseStore[T] { var zero T schema := ParseSchema[T]() store := &BaseStore[T]{ items: make(map[int]*T), maxID: 0, itemType: reflect.TypeOf(zero), indices: make(map[string]any), indexBuilders: make(map[string]IndexBuilder[T]), schema: schema, uniqueIndices: make(map[string]map[any]int), } // Initialize unique indices for fieldName, constraints := range schema.Constraints { for _, constraint := range constraints { if constraint.Type == ConstraintUnique { store.uniqueIndices[fieldName] = make(map[any]int) } } } // Auto-register indices for indexed fields store.registerSchemaIndices() return store } func (bs *BaseStore[T]) registerSchemaIndices() { for fieldName, indexName := range bs.schema.Indices { bs.RegisterIndex(indexName, BuildFieldLookupIndex[T](fieldName)) } } // ValidateConstraints checks all constraints for an item func (bs *BaseStore[T]) ValidateConstraints(id int, item *T) error { itemValue := reflect.ValueOf(item).Elem() for fieldName, constraints := range bs.schema.Constraints { fieldValue := itemValue.FieldByName(fieldName) if !fieldValue.IsValid() { continue } for _, constraint := range constraints { switch constraint.Type { case ConstraintRequired: if isZeroValue(fieldValue) { return fmt.Errorf("field %s is required", fieldName) } case ConstraintUnique: value := fieldValue.Interface() if existingID, exists := bs.uniqueIndices[fieldName][value]; exists && existingID != id { return fmt.Errorf("field %s value %v already exists", fieldName, value) } } } } return nil } func isZeroValue(v reflect.Value) bool { return v.Interface() == reflect.Zero(v.Type()).Interface() } func (bs *BaseStore[T]) updateUniqueIndices(id int, item *T, add bool) { itemValue := reflect.ValueOf(item).Elem() for fieldName := range bs.uniqueIndices { fieldValue := itemValue.FieldByName(fieldName) if !fieldValue.IsValid() { continue } value := fieldValue.Interface() if add { bs.uniqueIndices[fieldName][value] = id } else { delete(bs.uniqueIndices[fieldName], value) } } } // RegisterIndex registers an index builder function func (bs *BaseStore[T]) RegisterIndex(name string, builder IndexBuilder[T]) { bs.mu.Lock() defer bs.mu.Unlock() bs.indexBuilders[name] = builder } // GetIndex retrieves a named index func (bs *BaseStore[T]) GetIndex(name string) (any, bool) { bs.mu.RLock() defer bs.mu.RUnlock() index, exists := bs.indices[name] return index, exists } // RebuildIndices rebuilds all registered indices func (bs *BaseStore[T]) RebuildIndices() { bs.mu.Lock() defer bs.mu.Unlock() bs.rebuildIndicesUnsafe() } func (bs *BaseStore[T]) rebuildIndicesUnsafe() { allItems := make(map[int]*T, len(bs.items)) maps.Copy(allItems, bs.items) for name, builder := range bs.indexBuilders { bs.indices[name] = builder(allItems) } } // AddWithValidation adds item with constraint validation and index rebuild func (bs *BaseStore[T]) AddWithValidation(id int, item *T) error { bs.mu.Lock() defer bs.mu.Unlock() // Validate constraints if err := bs.ValidateConstraints(id, item); err != nil { return err } // Custom validation if validatable, ok := any(item).(Validatable); ok { if err := validatable.Validate(); err != nil { return err } } // Update unique indices bs.updateUniqueIndices(id, item, true) bs.items[id] = item if id > bs.maxID { bs.maxID = id } bs.rebuildIndicesUnsafe() return nil } // AddWithRebuild adds item with validation and index rebuild func (bs *BaseStore[T]) AddWithRebuild(id int, item *T) error { return bs.AddWithValidation(id, item) } // RemoveWithValidation removes item and updates constraints func (bs *BaseStore[T]) RemoveWithValidation(id int) { bs.mu.Lock() defer bs.mu.Unlock() if item, exists := bs.items[id]; exists { bs.updateUniqueIndices(id, item, false) } delete(bs.items, id) bs.rebuildIndicesUnsafe() } // RemoveWithRebuild removes item and rebuilds indices func (bs *BaseStore[T]) RemoveWithRebuild(id int) { bs.RemoveWithValidation(id) } // UpdateWithValidation updates item with validation and index rebuild func (bs *BaseStore[T]) UpdateWithValidation(id int, item *T) error { return bs.AddWithValidation(id, item) } // UpdateWithRebuild updates item with validation and index rebuild func (bs *BaseStore[T]) UpdateWithRebuild(id int, item *T) error { return bs.AddWithValidation(id, item) } // Find retrieves an item by ID func (bs *BaseStore[T]) Find(id int) (*T, bool) { bs.mu.RLock() defer bs.mu.RUnlock() item, exists := bs.items[id] return item, exists } // AllSorted returns all items using named sorted index func (bs *BaseStore[T]) AllSorted(indexName string) []*T { bs.mu.RLock() defer bs.mu.RUnlock() if index, exists := bs.indices[indexName]; exists { if sortedIDs, ok := index.([]int); ok { result := make([]*T, 0, len(sortedIDs)) for _, id := range sortedIDs { if item, exists := bs.items[id]; exists { result = append(result, item) } } return result } } // Fallback: return all items by ID order ids := make([]int, 0, len(bs.items)) for id := range bs.items { ids = append(ids, id) } sort.Ints(ids) result := make([]*T, 0, len(ids)) for _, id := range ids { result = append(result, bs.items[id]) } return result } // LookupByIndex finds single item using string lookup index func (bs *BaseStore[T]) LookupByIndex(indexName, key string) (*T, bool) { bs.mu.RLock() defer bs.mu.RUnlock() if index, exists := bs.indices[indexName]; exists { if lookupMap, ok := index.(map[string]int); ok { if id, found := lookupMap[key]; found { if item, exists := bs.items[id]; exists { return item, true } } } } return nil, false } // GroupByIndex returns items grouped by key func (bs *BaseStore[T]) GroupByIndex(indexName string, key any) []*T { bs.mu.RLock() defer bs.mu.RUnlock() if index, exists := bs.indices[indexName]; exists { switch groupMap := index.(type) { case map[int][]int: if intKey, ok := key.(int); ok { if ids, found := groupMap[intKey]; found { result := make([]*T, 0, len(ids)) for _, id := range ids { if item, exists := bs.items[id]; exists { result = append(result, item) } } return result } } case map[string][]int: if strKey, ok := key.(string); ok { if ids, found := groupMap[strKey]; found { result := make([]*T, 0, len(ids)) for _, id := range ids { if item, exists := bs.items[id]; exists { result = append(result, item) } } return result } } } } return []*T{} } // FilterByIndex returns items matching filter criteria func (bs *BaseStore[T]) FilterByIndex(indexName string, filterFunc func(*T) bool) []*T { bs.mu.RLock() defer bs.mu.RUnlock() var sourceIDs []int if index, exists := bs.indices[indexName]; exists { if sortedIDs, ok := index.([]int); ok { sourceIDs = sortedIDs } } if sourceIDs == nil { for id := range bs.items { sourceIDs = append(sourceIDs, id) } sort.Ints(sourceIDs) } var result []*T for _, id := range sourceIDs { if item, exists := bs.items[id]; exists && filterFunc(item) { result = append(result, item) } } return result } // GetNextID returns the next available ID atomically func (bs *BaseStore[T]) GetNextID() int { bs.mu.Lock() defer bs.mu.Unlock() bs.maxID++ return bs.maxID } // GetByID retrieves an item by ID func (bs *BaseStore[T]) GetByID(id int) (*T, bool) { return bs.Find(id) } // Add adds an item to the store (no validation) func (bs *BaseStore[T]) Add(id int, item *T) { bs.mu.Lock() defer bs.mu.Unlock() bs.items[id] = item if id > bs.maxID { bs.maxID = id } } // Remove removes an item from the store (no validation) func (bs *BaseStore[T]) Remove(id int) { bs.mu.Lock() defer bs.mu.Unlock() delete(bs.items, id) } // GetAll returns all items func (bs *BaseStore[T]) GetAll() map[int]*T { bs.mu.RLock() defer bs.mu.RUnlock() result := make(map[int]*T, len(bs.items)) maps.Copy(result, bs.items) return result } // Clear removes all items func (bs *BaseStore[T]) Clear() { bs.mu.Lock() defer bs.mu.Unlock() bs.items = make(map[int]*T) bs.maxID = 0 // Clear unique indices for fieldName := range bs.uniqueIndices { bs.uniqueIndices[fieldName] = make(map[any]int) } bs.rebuildIndicesUnsafe() } // Index Builder Functions // BuildFieldLookupIndex creates index for any field by name func BuildFieldLookupIndex[T any](fieldName string) IndexBuilder[T] { return func(allItems map[int]*T) any { index := make(map[string]int) for id, item := range allItems { itemValue := reflect.ValueOf(item).Elem() fieldValue := itemValue.FieldByName(fieldName) if !fieldValue.IsValid() { continue } key := fmt.Sprintf("%v", fieldValue.Interface()) index[key] = id } return index } } // BuildStringLookupIndex creates string-to-ID mapping func BuildStringLookupIndex[T any](keyFunc func(*T) string) IndexBuilder[T] { return func(allItems map[int]*T) any { index := make(map[string]int) for id, item := range allItems { key := keyFunc(item) index[key] = id } return index } } // BuildCaseInsensitiveLookupIndex creates lowercase string-to-ID mapping func BuildCaseInsensitiveLookupIndex[T any](keyFunc func(*T) string) IndexBuilder[T] { return func(allItems map[int]*T) any { index := make(map[string]int) for id, item := range allItems { key := strings.ToLower(keyFunc(item)) index[key] = id } return index } } // BuildIntGroupIndex creates int-to-[]ID mapping func BuildIntGroupIndex[T any](keyFunc func(*T) int) IndexBuilder[T] { return func(allItems map[int]*T) any { index := make(map[int][]int) for id, item := range allItems { key := keyFunc(item) index[key] = append(index[key], id) } // Sort each group by ID for key := range index { sort.Ints(index[key]) } return index } } // BuildStringGroupIndex creates string-to-[]ID mapping func BuildStringGroupIndex[T any](keyFunc func(*T) string) IndexBuilder[T] { return func(allItems map[int]*T) any { index := make(map[string][]int) for id, item := range allItems { key := keyFunc(item) index[key] = append(index[key], id) } // Sort each group by ID for key := range index { sort.Ints(index[key]) } return index } } // BuildSortedListIndex creates sorted []ID list func BuildSortedListIndex[T any](sortFunc func(*T, *T) bool) IndexBuilder[T] { return func(allItems map[int]*T) any { ids := make([]int, 0, len(allItems)) for id := range allItems { ids = append(ids, id) } sort.Slice(ids, func(i, j int) bool { return sortFunc(allItems[ids[i]], allItems[ids[j]]) }) return ids } } // BuildFilteredIntGroupIndex creates int-to-[]ID mapping for items passing filter func BuildFilteredIntGroupIndex[T any](filterFunc func(*T) bool, keyFunc func(*T) int) IndexBuilder[T] { return func(allItems map[int]*T) any { index := make(map[int][]int) for id, item := range allItems { if filterFunc(item) { key := keyFunc(item) index[key] = append(index[key], id) } } // Sort each group by ID for key := range index { sort.Ints(index[key]) } return index } } // BuildFilteredStringGroupIndex creates string-to-[]ID mapping for items passing filter func BuildFilteredStringGroupIndex[T any](filterFunc func(*T) bool, keyFunc func(*T) string) IndexBuilder[T] { return func(allItems map[int]*T) any { index := make(map[string][]int) for id, item := range allItems { if filterFunc(item) { key := keyFunc(item) index[key] = append(index[key], id) } } // Sort each group by ID for key := range index { sort.Ints(index[key]) } return index } } // NewSingleton creates singleton store pattern with sync.Once func NewSingleton[S any](initFunc func() *S) func() *S { var store *S var once sync.Once return func() *S { once.Do(func() { store = initFunc() }) return store } }