package chat import ( "context" "fmt" "strings" "sync" "time" ) // ChatService provides high-level chat system management type ChatService struct { manager *ChatManager mu sync.RWMutex } // NewChatService creates a new chat service instance func NewChatService(database ChannelDatabase, clientManager ClientManager, playerManager PlayerManager, languageProcessor LanguageProcessor) *ChatService { return &ChatService{ manager: NewChatManager(database, clientManager, playerManager, languageProcessor), } } // Initialize initializes the chat service and loads world channels func (cs *ChatService) Initialize(ctx context.Context) error { cs.mu.Lock() defer cs.mu.Unlock() return cs.manager.Initialize(ctx) } // ProcessChannelCommand processes chat channel commands (join, leave, create, etc.) func (cs *ChatService) ProcessChannelCommand(characterID int32, command, channelName string, args ...string) error { cs.mu.RLock() defer cs.mu.RUnlock() switch strings.ToLower(command) { case "join": password := "" if len(args) > 0 { password = args[0] } return cs.manager.JoinChannel(characterID, channelName, password) case "leave": return cs.manager.LeaveChannel(characterID, channelName) case "create": password := "" if len(args) > 0 { password = args[0] } return cs.manager.CreateChannel(channelName, password) case "tell", "say": if len(args) == 0 { return fmt.Errorf("no message provided") } message := strings.Join(args, " ") return cs.manager.TellChannel(characterID, channelName, message) case "who", "list": return cs.manager.SendChannelUserList(characterID, channelName) default: return fmt.Errorf("unknown channel command: %s", command) } } // SendChannelMessage sends a message to a channel func (cs *ChatService) SendChannelMessage(senderID int32, channelName string, message string, customName ...string) error { cs.mu.RLock() defer cs.mu.RUnlock() return cs.manager.TellChannel(senderID, channelName, message, customName...) } // JoinChannel adds a character to a channel func (cs *ChatService) JoinChannel(characterID int32, channelName string, password ...string) error { cs.mu.RLock() defer cs.mu.RUnlock() return cs.manager.JoinChannel(characterID, channelName, password...) } // LeaveChannel removes a character from a channel func (cs *ChatService) LeaveChannel(characterID int32, channelName string) error { cs.mu.RLock() defer cs.mu.RUnlock() return cs.manager.LeaveChannel(characterID, channelName) } // LeaveAllChannels removes a character from all channels func (cs *ChatService) LeaveAllChannels(characterID int32) error { cs.mu.RLock() defer cs.mu.RUnlock() return cs.manager.LeaveAllChannels(characterID) } // CreateChannel creates a new custom channel func (cs *ChatService) CreateChannel(channelName string, password ...string) error { cs.mu.RLock() defer cs.mu.RUnlock() return cs.manager.CreateChannel(channelName, password...) } // GetWorldChannelList returns available world channels for a character func (cs *ChatService) GetWorldChannelList(characterID int32) ([]ChannelInfo, error) { cs.mu.RLock() defer cs.mu.RUnlock() return cs.manager.GetWorldChannelList(characterID) } // ChannelExists checks if a channel exists func (cs *ChatService) ChannelExists(channelName string) bool { cs.mu.RLock() defer cs.mu.RUnlock() return cs.manager.ChannelExists(channelName) } // IsInChannel checks if a character is in a channel func (cs *ChatService) IsInChannel(characterID int32, channelName string) bool { cs.mu.RLock() defer cs.mu.RUnlock() return cs.manager.IsInChannel(characterID, channelName) } // GetChannelInfo returns information about a specific channel func (cs *ChatService) GetChannelInfo(channelName string) (*ChannelInfo, error) { cs.mu.RLock() defer cs.mu.RUnlock() channel := cs.manager.GetChannel(channelName) if channel == nil { return nil, fmt.Errorf("channel %s not found", channelName) } info := channel.GetChannelInfo() return &info, nil } // GetStatistics returns chat system statistics func (cs *ChatService) GetStatistics() ChatStatistics { cs.mu.RLock() defer cs.mu.RUnlock() return cs.manager.GetStatistics() } // SendChannelUserList sends the user list for a channel to a character func (cs *ChatService) SendChannelUserList(requesterID int32, channelName string) error { cs.mu.RLock() defer cs.mu.RUnlock() return cs.manager.SendChannelUserList(requesterID, channelName) } // ValidateChannelName checks if a channel name is valid func (cs *ChatService) ValidateChannelName(channelName string) error { if len(channelName) == 0 { return fmt.Errorf("channel name cannot be empty") } if len(channelName) > MaxChannelNameLength { return fmt.Errorf("channel name too long: %d > %d", len(channelName), MaxChannelNameLength) } // Check for invalid characters if strings.ContainsAny(channelName, " \t\n\r") { return fmt.Errorf("channel name cannot contain whitespace") } return nil } // ValidateChannelPassword checks if a channel password is valid func (cs *ChatService) ValidateChannelPassword(password string) error { if len(password) > MaxChannelPasswordLength { return fmt.Errorf("channel password too long: %d > %d", len(password), MaxChannelPasswordLength) } return nil } // GetChannelMembers returns the list of members in a channel func (cs *ChatService) GetChannelMembers(channelName string) ([]int32, error) { cs.mu.RLock() defer cs.mu.RUnlock() channel := cs.manager.GetChannel(channelName) if channel == nil { return nil, fmt.Errorf("channel %s not found", channelName) } return channel.GetMembers(), nil } // CleanupEmptyChannels removes empty custom channels (called periodically) func (cs *ChatService) CleanupEmptyChannels() int { cs.mu.Lock() defer cs.mu.Unlock() removed := 0 for name, channel := range cs.manager.channels { if channel.channelType == ChannelTypeCustom && channel.IsEmpty() { // Check if channel has been empty for a reasonable time if time.Since(channel.created) > 5*time.Minute { delete(cs.manager.channels, name) removed++ } } } return removed } // BroadcastSystemMessage sends a system message to all members of a channel func (cs *ChatService) BroadcastSystemMessage(channelName string, message string, systemName string) error { cs.mu.RLock() defer cs.mu.RUnlock() return cs.manager.TellChannel(0, channelName, message, systemName) } // GetActiveChannels returns a list of channels that have active members func (cs *ChatService) GetActiveChannels() []string { cs.mu.RLock() defer cs.mu.RUnlock() var activeChannels []string for name, channel := range cs.manager.channels { if !channel.IsEmpty() { activeChannels = append(activeChannels, name) } } return activeChannels } // GetChannelsByType returns channels of a specific type func (cs *ChatService) GetChannelsByType(channelType int) []string { cs.mu.RLock() defer cs.mu.RUnlock() var channels []string for name, channel := range cs.manager.channels { if channel.channelType == channelType { channels = append(channels, name) } } return channels } // ProcessChannelFilter applies filtering to channel lists based on player criteria func (cs *ChatService) ProcessChannelFilter(characterID int32, filter ChannelFilter) ([]ChannelInfo, error) { cs.mu.RLock() defer cs.mu.RUnlock() playerInfo, err := cs.manager.playerManager.GetPlayerInfo(characterID) if err != nil { return nil, fmt.Errorf("failed to get player info: %w", err) } var filteredChannels []ChannelInfo for _, channel := range cs.manager.channels { // Apply type filters if !filter.IncludeWorld && channel.channelType == ChannelTypeWorld { continue } if !filter.IncludeCustom && channel.channelType == ChannelTypeCustom { continue } // Apply level range filters if filter.MinLevel > 0 && playerInfo.Level < filter.MinLevel { continue } if filter.MaxLevel > 0 && playerInfo.Level > filter.MaxLevel { continue } // Apply race filter if filter.Race > 0 && playerInfo.Race != filter.Race { continue } // Apply class filter if filter.Class > 0 && playerInfo.Class != filter.Class { continue } // Check if player can actually join the channel if cs.manager.canJoinChannel(playerInfo.Level, playerInfo.Race, playerInfo.Class, channel.levelRestriction, channel.raceRestriction, channel.classRestriction) { filteredChannels = append(filteredChannels, channel.GetChannelInfo()) } } return filteredChannels, nil }