package collections import ( "context" "fmt" "sync" ) // NewCollectionManager creates a new collection manager instance func NewCollectionManager(database CollectionDatabase, itemLookup ItemLookup) *CollectionManager { return &CollectionManager{ masterList: NewMasterCollectionList(database), database: database, itemLookup: itemLookup, } } // Initialize initializes the collection system by loading all collections func (cm *CollectionManager) Initialize(ctx context.Context) error { return cm.masterList.Initialize(ctx, cm.itemLookup) } // GetMasterList returns the master collection list func (cm *CollectionManager) GetMasterList() *MasterCollectionList { return cm.masterList } // CreatePlayerCollectionList creates a new player collection list func (cm *CollectionManager) CreatePlayerCollectionList(characterID int32) *PlayerCollectionList { return NewPlayerCollectionList(characterID, cm.database) } // GetCollection returns a collection by ID from the master list func (cm *CollectionManager) GetCollection(collectionID int32) *Collection { return cm.masterList.GetCollection(collectionID) } // GetCollectionCopy returns a copy of a collection by ID func (cm *CollectionManager) GetCollectionCopy(collectionID int32) *Collection { return cm.masterList.GetCollectionCopy(collectionID) } // ProcessItemFound processes when a player finds an item across all collections func (cm *CollectionManager) ProcessItemFound(playerList *PlayerCollectionList, itemID int32) ([]*Collection, error) { if playerList == nil { return nil, fmt.Errorf("player collection list is nil") } return playerList.ProcessItemFound(itemID, cm.masterList) } // CompleteCollection processes collection completion for a player func (cm *CollectionManager) CompleteCollection(playerList *PlayerCollectionList, collectionID int32, rewardProvider RewardProvider) error { if playerList == nil { return fmt.Errorf("player collection list is nil") } collection := playerList.GetCollection(collectionID) if collection == nil { return fmt.Errorf("collection %d not found in player list", collectionID) } if !collection.GetIsReadyToTurnIn() { return fmt.Errorf("collection %d is not ready to complete", collectionID) } // Give rewards if provider is available if rewardProvider != nil { characterID := playerList.GetCharacterID() // Give coin reward if coin := collection.GetRewardCoin(); coin > 0 { if err := rewardProvider.GiveCoin(characterID, coin); err != nil { return fmt.Errorf("failed to give coin reward: %w", err) } } // Give XP reward if xp := collection.GetRewardXP(); xp > 0 { if err := rewardProvider.GiveXP(characterID, xp); err != nil { return fmt.Errorf("failed to give XP reward: %w", err) } } // Give item rewards for _, reward := range collection.GetRewardItems() { if err := rewardProvider.GiveItem(characterID, reward.ItemID, reward.Quantity); err != nil { return fmt.Errorf("failed to give item reward %d: %w", reward.ItemID, err) } } } // Mark collection as completed return playerList.CompleteCollection(collectionID) } // GetAvailableCollections returns collections available to a player based on level func (cm *CollectionManager) GetAvailableCollections(playerLevel int8) []*Collection { return cm.masterList.GetCollectionsByLevel(0, playerLevel) } // GetCollectionsByCategory returns collections in a specific category func (cm *CollectionManager) GetCollectionsByCategory(category string) []*Collection { return cm.masterList.GetCollectionsByCategory(category) } // GetAllCategories returns all collection categories func (cm *CollectionManager) GetAllCategories() []string { return cm.masterList.GetCategories() } // SearchCollections searches for collections by name func (cm *CollectionManager) SearchCollections(searchTerm string) []*Collection { return cm.masterList.FindCollectionsByName(searchTerm) } // ValidateSystemIntegrity validates the integrity of the collection system func (cm *CollectionManager) ValidateSystemIntegrity() []error { return cm.masterList.ValidateIntegrity(cm.itemLookup) } // GetSystemStatistics returns overall collection system statistics func (cm *CollectionManager) GetSystemStatistics() CollectionStatistics { return cm.masterList.GetStatistics() } // CollectionService provides high-level collection system services type CollectionService struct { manager *CollectionManager playerLists map[int32]*PlayerCollectionList mu sync.RWMutex clientManager ClientManager eventHandler CollectionEventHandler logger LogHandler } // NewCollectionService creates a new collection service func NewCollectionService(database CollectionDatabase, itemLookup ItemLookup, clientManager ClientManager) *CollectionService { return &CollectionService{ manager: NewCollectionManager(database, itemLookup), playerLists: make(map[int32]*PlayerCollectionList), clientManager: clientManager, } } // SetEventHandler sets the collection event handler func (cs *CollectionService) SetEventHandler(handler CollectionEventHandler) { cs.eventHandler = handler } // SetLogger sets the logger for the service func (cs *CollectionService) SetLogger(logger LogHandler) { cs.logger = logger } // Initialize initializes the collection service func (cs *CollectionService) Initialize(ctx context.Context) error { return cs.manager.Initialize(ctx) } // LoadPlayerCollections loads collections for a specific player func (cs *CollectionService) LoadPlayerCollections(ctx context.Context, characterID int32) error { cs.mu.Lock() defer cs.mu.Unlock() playerList := cs.manager.CreatePlayerCollectionList(characterID) if err := playerList.Initialize(ctx, cs.manager.GetMasterList()); err != nil { return fmt.Errorf("failed to initialize player collections: %w", err) } cs.playerLists[characterID] = playerList if cs.logger != nil { cs.logger.LogDebug("collections", "Loaded %d collections for character %d", playerList.Size(), characterID) } return nil } // UnloadPlayerCollections unloads collections for a player (when they log out) func (cs *CollectionService) UnloadPlayerCollections(ctx context.Context, characterID int32) error { cs.mu.Lock() defer cs.mu.Unlock() playerList, exists := cs.playerLists[characterID] if !exists { return nil // Already unloaded } // Save any pending changes if err := playerList.SaveCollections(ctx); err != nil { if cs.logger != nil { cs.logger.LogError("collections", "Failed to save collections for character %d: %v", characterID, err) } return fmt.Errorf("failed to save collections: %w", err) } delete(cs.playerLists, characterID) if cs.logger != nil { cs.logger.LogDebug("collections", "Unloaded collections for character %d", characterID) } return nil } // ProcessItemFound processes when a player finds an item func (cs *CollectionService) ProcessItemFound(characterID, itemID int32) error { cs.mu.RLock() playerList, exists := cs.playerLists[characterID] cs.mu.RUnlock() if !exists { return fmt.Errorf("player collections not loaded for character %d", characterID) } updatedCollections, err := cs.manager.ProcessItemFound(playerList, itemID) if err != nil { return fmt.Errorf("failed to process found item: %w", err) } // Notify client and event handler of updates for _, collection := range updatedCollections { if cs.clientManager != nil { cs.clientManager.SendCollectionUpdate(characterID, collection) } if cs.eventHandler != nil { cs.eventHandler.OnItemFound(characterID, collection.GetID(), itemID) } if cs.logger != nil { cs.logger.LogDebug("collections", "Character %d found item %d for collection %d (%s)", characterID, itemID, collection.GetID(), collection.GetName()) } } return nil } // CompleteCollection processes collection completion func (cs *CollectionService) CompleteCollection(characterID, collectionID int32, rewardProvider RewardProvider) error { cs.mu.RLock() playerList, exists := cs.playerLists[characterID] cs.mu.RUnlock() if !exists { return fmt.Errorf("player collections not loaded for character %d", characterID) } collection := playerList.GetCollection(collectionID) if collection == nil { return fmt.Errorf("collection %d not found for character %d", collectionID, characterID) } // Complete the collection if err := cs.manager.CompleteCollection(playerList, collectionID, rewardProvider); err != nil { return fmt.Errorf("failed to complete collection: %w", err) } // Notify client and event handler if cs.clientManager != nil { cs.clientManager.SendCollectionComplete(characterID, collection) } if cs.eventHandler != nil { rewards := collection.GetRewardItems() selectableRewards := collection.GetSelectableRewardItems() allRewards := append(rewards, selectableRewards...) cs.eventHandler.OnCollectionCompleted(characterID, collectionID) cs.eventHandler.OnRewardClaimed(characterID, collectionID, allRewards, collection.GetRewardCoin(), collection.GetRewardXP()) } if cs.logger != nil { cs.logger.LogInfo("collections", "Character %d completed collection %d (%s)", characterID, collectionID, collection.GetName()) } return nil } // GetPlayerCollections returns all collections for a player func (cs *CollectionService) GetPlayerCollections(characterID int32) ([]*Collection, error) { cs.mu.RLock() playerList, exists := cs.playerLists[characterID] cs.mu.RUnlock() if !exists { return nil, fmt.Errorf("player collections not loaded for character %d", characterID) } return playerList.GetAllCollections(), nil } // GetPlayerCollectionProgress returns detailed progress for all player collections func (cs *CollectionService) GetPlayerCollectionProgress(characterID int32) ([]CollectionProgress, error) { cs.mu.RLock() playerList, exists := cs.playerLists[characterID] cs.mu.RUnlock() if !exists { return nil, fmt.Errorf("player collections not loaded for character %d", characterID) } return playerList.GetCollectionProgress(), nil } // SendCollectionList sends available collections to a player func (cs *CollectionService) SendCollectionList(characterID int32, playerLevel int8) error { if cs.clientManager == nil { return fmt.Errorf("client manager not available") } collections := cs.manager.GetAvailableCollections(playerLevel) collectionInfos := make([]CollectionInfo, len(collections)) for i, collection := range collections { collectionInfos[i] = collection.GetCollectionInfo() } return cs.clientManager.SendCollectionList(characterID, collectionInfos) } // SaveAllPlayerCollections saves all loaded player collections func (cs *CollectionService) SaveAllPlayerCollections(ctx context.Context) error { cs.mu.RLock() playerLists := make(map[int32]*PlayerCollectionList) for characterID, playerList := range cs.playerLists { playerLists[characterID] = playerList } cs.mu.RUnlock() var saveErrors []error for characterID, playerList := range playerLists { if err := playerList.SaveCollections(ctx); err != nil { saveErrors = append(saveErrors, fmt.Errorf("character %d: %w", characterID, err)) if cs.logger != nil { cs.logger.LogError("collections", "Failed to save collections for character %d: %v", characterID, err) } } } if len(saveErrors) > 0 { return fmt.Errorf("failed to save some player collections: %v", saveErrors) } return nil } // GetLoadedPlayerCount returns the number of players with loaded collections func (cs *CollectionService) GetLoadedPlayerCount() int { cs.mu.RLock() defer cs.mu.RUnlock() return len(cs.playerLists) } // IsPlayerLoaded checks if a player's collections are loaded func (cs *CollectionService) IsPlayerLoaded(characterID int32) bool { cs.mu.RLock() defer cs.mu.RUnlock() _, exists := cs.playerLists[characterID] return exists } // GetMasterCollection returns a collection from the master list func (cs *CollectionService) GetMasterCollection(collectionID int32) *Collection { return cs.manager.GetCollection(collectionID) } // GetAllCategories returns all collection categories func (cs *CollectionService) GetAllCategories() []string { return cs.manager.GetAllCategories() } // SearchCollections searches for collections by name func (cs *CollectionService) SearchCollections(searchTerm string) []*Collection { return cs.manager.SearchCollections(searchTerm) }