package chat import ( "fmt" "slices" "time" "eq2emu/internal/database" ) // Channel represents a chat channel with membership and message routing capabilities type Channel struct { ID int32 `json:"id"` // Channel ID Name string `json:"name"` // Channel name Password string `json:"-"` // Channel password (hidden from JSON) ChannelType int `json:"type"` // Channel type (world/custom) LevelRestriction int32 `json:"level_restriction"` RaceRestriction int32 `json:"race_restriction"` ClassRestriction int32 `json:"class_restriction"` DiscordEnabled bool `json:"discord_enabled"` Created time.Time `json:"created"` Updated time.Time `json:"updated"` members []int32 `json:"-"` // Character IDs (not persisted) db *database.Database `json:"-"` // Database connection isNew bool `json:"-"` // Whether this is a new channel } // New creates a new channel with the given database func New(db *database.Database) *Channel { return &Channel{ db: db, isNew: true, members: make([]int32, 0), Created: time.Now(), Updated: time.Now(), } } // NewWithData creates a new channel with data func NewWithData(id int32, name string, channelType int, db *database.Database) *Channel { return &Channel{ ID: id, Name: name, ChannelType: channelType, db: db, isNew: true, members: make([]int32, 0), Created: time.Now(), Updated: time.Now(), } } // Load loads a channel by ID from the database func Load(db *database.Database, id int32) (*Channel, error) { channel := &Channel{ db: db, isNew: false, members: make([]int32, 0), } query := `SELECT id, name, password, type, level_restriction, race_restriction, class_restriction, discord_enabled, created_at, updated_at FROM channels WHERE id = ?` row := db.QueryRow(query, id) err := row.Scan(&channel.ID, &channel.Name, &channel.Password, &channel.ChannelType, &channel.LevelRestriction, &channel.RaceRestriction, &channel.ClassRestriction, &channel.DiscordEnabled, &channel.Created, &channel.Updated) if err != nil { return nil, fmt.Errorf("failed to load channel %d: %w", id, err) } return channel, nil } // LoadByName loads a channel by name from the database func LoadByName(db *database.Database, name string) (*Channel, error) { channel := &Channel{ db: db, isNew: false, members: make([]int32, 0), } query := `SELECT id, name, password, type, level_restriction, race_restriction, class_restriction, discord_enabled, created_at, updated_at FROM channels WHERE name = ?` row := db.QueryRow(query, name) err := row.Scan(&channel.ID, &channel.Name, &channel.Password, &channel.ChannelType, &channel.LevelRestriction, &channel.RaceRestriction, &channel.ClassRestriction, &channel.DiscordEnabled, &channel.Created, &channel.Updated) if err != nil { return nil, fmt.Errorf("failed to load channel %s: %w", name, err) } return channel, nil } // GetID returns the channel ID (implements Identifiable interface) func (c *Channel) GetID() int32 { return c.ID } // SetName sets the channel name func (c *Channel) SetName(name string) { c.Name = name c.Updated = time.Now() } // SetPassword sets the channel password func (c *Channel) SetPassword(password string) { c.Password = password c.Updated = time.Now() } // SetType sets the channel type func (c *Channel) SetType(channelType int) { c.ChannelType = channelType c.Updated = time.Now() } // SetLevelRestriction sets the minimum level required to join func (c *Channel) SetLevelRestriction(level int32) { c.LevelRestriction = level c.Updated = time.Now() } // SetRacesAllowed sets the race bitmask for allowed races func (c *Channel) SetRacesAllowed(races int32) { c.RaceRestriction = races c.Updated = time.Now() } // SetClassesAllowed sets the class bitmask for allowed classes func (c *Channel) SetClassesAllowed(classes int32) { c.ClassRestriction = classes c.Updated = time.Now() } // GetName returns the channel name func (c *Channel) GetName() string { return c.Name } // GetType returns the channel type func (c *Channel) GetType() int { return c.ChannelType } // GetNumClients returns the number of clients in the channel func (c *Channel) GetNumClients() int { return len(c.members) } // HasPassword returns true if the channel has a password func (c *Channel) HasPassword() bool { return c.Password != "" } // PasswordMatches checks if the provided password matches the channel password func (c *Channel) PasswordMatches(password string) bool { return c.Password == password } // CanJoinChannelByLevel checks if a player's level meets the channel requirements func (c *Channel) CanJoinChannelByLevel(level int32) bool { return level >= c.LevelRestriction } // CanJoinChannelByRace checks if a player's race is allowed in the channel func (c *Channel) CanJoinChannelByRace(raceID int32) bool { return c.RaceRestriction == NoRaceRestriction || (c.RaceRestriction&(1<