package store import ( "encoding/json" "fmt" "maps" "os" "path/filepath" "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 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] } // NewBaseStore creates a new base store for type T func NewBaseStore[T any]() *BaseStore[T] { var zero T return &BaseStore[T]{ items: make(map[int]*T), maxID: 0, itemType: reflect.TypeOf(zero), indices: make(map[string]any), indexBuilders: make(map[string]IndexBuilder[T]), } } // 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) } } // AddWithRebuild adds item with validation and index rebuild func (bs *BaseStore[T]) AddWithRebuild(id int, item *T) error { bs.mu.Lock() defer bs.mu.Unlock() if validatable, ok := any(item).(Validatable); ok { if err := validatable.Validate(); err != nil { return err } } bs.items[id] = item if id > bs.maxID { bs.maxID = id } bs.rebuildIndicesUnsafe() return nil } // RemoveWithRebuild removes item and rebuilds indices func (bs *BaseStore[T]) RemoveWithRebuild(id int) { bs.mu.Lock() defer bs.mu.Unlock() delete(bs.items, id) bs.rebuildIndicesUnsafe() } // UpdateWithRebuild updates item with validation and index rebuild func (bs *BaseStore[T]) UpdateWithRebuild(id int, item *T) error { return bs.AddWithRebuild(id, item) } // Common Query Methods // 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 } // 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 } } // 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 } } // 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 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 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 bs.rebuildIndicesUnsafe() } // LoadFromJSON loads items from JSON using reflection func (bs *BaseStore[T]) LoadFromJSON(filename string) error { bs.mu.Lock() defer bs.mu.Unlock() data, err := os.ReadFile(filename) if err != nil { if os.IsNotExist(err) { return nil } return fmt.Errorf("failed to read JSON: %w", err) } if len(data) == 0 { return nil } // Create slice of pointers to T sliceType := reflect.SliceOf(reflect.PointerTo(bs.itemType)) slicePtr := reflect.New(sliceType) if err := json.Unmarshal(data, slicePtr.Interface()); err != nil { return fmt.Errorf("failed to unmarshal JSON: %w", err) } // Clear existing data bs.items = make(map[int]*T) bs.maxID = 0 // Extract items using reflection slice := slicePtr.Elem() for i := 0; i < slice.Len(); i++ { item := slice.Index(i).Interface().(*T) // Get ID using reflection itemValue := reflect.ValueOf(item).Elem() idField := itemValue.FieldByName("ID") if !idField.IsValid() { return fmt.Errorf("item type must have an ID field") } id := int(idField.Int()) bs.items[id] = item if id > bs.maxID { bs.maxID = id } } return nil } // SaveToJSON saves items to JSON atomically with consistent ID ordering func (bs *BaseStore[T]) SaveToJSON(filename string) error { bs.mu.RLock() defer bs.mu.RUnlock() // Get sorted IDs for consistent ordering ids := make([]int, 0, len(bs.items)) for id := range bs.items { ids = append(ids, id) } sort.Ints(ids) // Build items slice in ID order items := make([]*T, 0, len(bs.items)) for _, id := range ids { items = append(items, bs.items[id]) } data, err := json.MarshalIndent(items, "", "\t") if err != nil { return fmt.Errorf("failed to marshal to JSON: %w", err) } // Atomic write tempFile := filename + ".tmp" if err := os.WriteFile(tempFile, data, 0644); err != nil { return fmt.Errorf("failed to write temp JSON: %w", err) } if err := os.Rename(tempFile, filename); err != nil { os.Remove(tempFile) return fmt.Errorf("failed to rename temp JSON: %w", err) } return nil } // LoadData loads from JSON file or starts empty func (bs *BaseStore[T]) LoadData(dataPath string) error { if err := bs.LoadFromJSON(dataPath); err != nil { if os.IsNotExist(err) { fmt.Println("No existing data found, starting with empty store") return nil } return fmt.Errorf("failed to load from JSON: %w", err) } fmt.Printf("Loaded %d items from %s\n", len(bs.items), dataPath) bs.RebuildIndices() // Rebuild indices after loading return nil } // SaveData saves to JSON file func (bs *BaseStore[T]) SaveData(dataPath string) error { // Ensure directory exists dataDir := filepath.Dir(dataPath) if err := os.MkdirAll(dataDir, 0755); err != nil { return fmt.Errorf("failed to create data directory: %w", err) } if err := bs.SaveToJSON(dataPath); err != nil { return fmt.Errorf("failed to save to JSON: %w", err) } fmt.Printf("Saved %d items to %s\n", len(bs.items), dataPath) return nil } // 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 } }