package main

import (
	"fmt"
	"log"
	"strings"
	"time"
)

// IRCMessage represents a parsed IRC message with IRCv3 support
type IRCMessage struct {
	Tags    map[string]string
	Prefix  string
	Command string
	Params  []string
}

// FormatMessage formats an IRC message with IRCv3 tags
func (m *IRCMessage) FormatMessage() string {
	var parts []string

	// Add tags if present
	if len(m.Tags) > 0 {
		var tagParts []string
		for key, value := range m.Tags {
			if value == "" {
				tagParts = append(tagParts, key)
			} else {
				tagParts = append(tagParts, fmt.Sprintf("%s=%s", key, value))
			}
		}
		parts = append(parts, "@"+strings.Join(tagParts, ";"))
	}

	// Add prefix if present
	if m.Prefix != "" {
		parts = append(parts, ":"+m.Prefix)
	}

	// Add command
	parts = append(parts, m.Command)

	// Add parameters
	parts = append(parts, m.Params...)

	return strings.Join(parts, " ")
}

// AddServerTime adds server-time tag to message
func (c *Client) AddServerTime(msg *IRCMessage) {
	if c.HasCapability("server-time") {
		if msg.Tags == nil {
			msg.Tags = make(map[string]string)
		}
		msg.Tags["time"] = time.Now().UTC().Format(time.RFC3339Nano)
	}
}

// AddAccountTag adds account tag if user is authenticated
func (c *Client) AddAccountTag(msg *IRCMessage) {
	if c.HasCapability("account-tag") && c.Account() != "" {
		if msg.Tags == nil {
			msg.Tags = make(map[string]string)
		}
		msg.Tags["account"] = c.Account()
	}
}

// IRC numeric reply codes
const (
	RPL_WELCOME       = 001
	RPL_YOURHOST      = 002
	RPL_CREATED       = 003
	RPL_MYINFO        = 004
	RPL_ISUPPORT      = 005
	RPL_USERHOST      = 302
	RPL_ISON          = 303
	RPL_AWAY          = 301
	RPL_UNAWAY        = 305
	RPL_NOWAWAY       = 306
	RPL_WHOISUSER     = 311
	RPL_WHOISSERVER   = 312
	RPL_WHOISOPERATOR = 313
	RPL_WHOISIDLE     = 317
	RPL_ENDOFWHOIS    = 318
	RPL_WHOISCHANNELS = 319
	RPL_WHOISHOST     = 378
	RPL_LISTSTART     = 321
	RPL_LIST          = 322
	RPL_LISTEND       = 323
	RPL_CHANNELMODEIS = 324
	RPL_NOTOPIC       = 331
	RPL_TOPIC         = 332
	RPL_TOPICWHOTIME  = 333
	RPL_NAMREPLY      = 353
	RPL_ENDOFNAMES    = 366
	RPL_MOTDSTART     = 375
	RPL_MOTD          = 372
	RPL_ENDOFMOTD     = 376
	RPL_UMODEIS       = 221
	RPL_INVITING      = 341
	RPL_YOUREOPER     = 381
	// New commands
	RPL_TIME          = 391
	RPL_VERSION       = 351
	RPL_ADMINME       = 256
	RPL_ADMINLOC1     = 257
	RPL_ADMINLOC2     = 258
	RPL_ADMINEMAIL    = 259
	RPL_INFO          = 371
	RPL_ENDOFINFO     = 374
	RPL_LUSERCLIENT   = 251
	RPL_LUSEROP       = 252
	RPL_LUSERUNKNOWN  = 253
	RPL_LUSERCHANNELS = 254
	RPL_LUSERME       = 255
	RPL_STATSCOMMANDS = 212
	RPL_STATSOLINE    = 243
	RPL_STATSUPTIME   = 242
	RPL_STATSLINKINFO = 211
	RPL_ENDOFSTATS    = 219
	// SILENCE command numerics
	RPL_SILELIST      = 271
	RPL_ENDOFSILELIST = 272
	ERR_SILELISTFULL  = 511
	// MONITOR command numerics (IRCv3)
	RPL_MONONLINE    = 730
	RPL_MONOFFLINE   = 731
	RPL_MONLIST      = 732
	RPL_ENDOFMONLIST = 733
	ERR_MONLISTFULL  = 734
	// Ban list numerics
	RPL_BANLIST      = 367
	RPL_ENDOFBANLIST = 368
	// SASL authentication numerics
	RPL_SASLSUCCESS       = 903
	ERR_SASLFAIL          = 904
	ERR_SASLNOTSUPP       = 908
	ERR_SASLABORTED       = 906
	ERR_NOSUCHNICK        = 401
	ERR_NOSUCHSERVER      = 402
	ERR_NOSUCHCHANNEL     = 403
	ERR_CANNOTSENDTOCHAN  = 404
	ERR_TOOMANYCHANNELS   = 405
	ERR_WASNOSUCHNICK     = 406
	ERR_TOOMANYTARGETS    = 407
	ERR_NOORIGIN          = 409
	ERR_NORECIPIENT       = 411
	ERR_NOTEXTTOSEND      = 412
	ERR_UNKNOWNCOMMAND    = 421
	ERR_NOMOTD            = 422
	ERR_NONICKNAMEGIVEN   = 431
	ERR_ERRONEUSNICKNAME  = 432
	ERR_NICKNAMEINUSE     = 433
	ERR_NICKCOLLISION     = 436
	ERR_USERNOTINCHANNEL  = 441
	ERR_NOTONCHANNEL      = 442
	ERR_USERONCHANNEL     = 443
	ERR_NOLOGIN           = 444
	ERR_SUMMONDISABLED    = 445
	ERR_USERSDISABLED     = 446
	ERR_NOTREGISTERED     = 451
	ERR_NEEDMOREPARAMS    = 461
	ERR_ALREADYREGISTRED  = 462
	ERR_NOPERMFORHOST     = 463
	ERR_PASSWDMISMATCH    = 464
	ERR_YOUREBANNEDCREEP  = 465
	ERR_YOUWILLBEBANNED   = 466
	ERR_KEYSET            = 467
	ERR_CHANNELISFULL     = 471
	ERR_UNKNOWNMODE       = 472
	ERR_INVITEONLYCHAN    = 473
	ERR_BANNEDFROMCHAN    = 474
	ERR_BADCHANNELKEY     = 475
	ERR_BADCHANMASK       = 476
	ERR_NOCHANMODES       = 477
	ERR_BANLISTFULL       = 478
	ERR_MODERATED         = 494 // Cannot send to channel (channel is moderated)
	ERR_QUIETED           = 404 // Cannot send to channel (you are quieted)
	ERR_NOPRIVILEGES      = 481
	ERR_CHANOPRIVSNEEDED  = 482
	ERR_CANTKILLSERVER    = 483
	ERR_RESTRICTED        = 484
	ERR_UNIQOPPRIVSNEEDED = 485
	ERR_NOOPERHOST        = 491
	ERR_UMODEUNKNOWNFLAG  = 501
	ERR_USERSDONTMATCH    = 502
	RPL_SNOMASK           = 8
	RPL_GLOBALNOTICE      = 710
	RPL_OPERWALL          = 711
	// New command numerics
	RPL_ENDOFWHOWAS       = 369
	RPL_RULESSTART        = 308
	RPL_RULES             = 232
	RPL_ENDOFRULES        = 309
	RPL_MAP               = 006
	RPL_MAPEND            = 007
	RPL_KNOCKDLVR         = 711
	ERR_KNOCKONCHAN       = 713
	ERR_CHANOPEN          = 713
	ERR_INVALIDUSERNAME   = 468
)

// handleNick handles NICK command
func (c *Client) handleNick(parts []string) {
	if len(parts) < 2 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "NICK :Not enough parameters")
		return
	}

	newNick := parts[1]
	if len(newNick) > 0 && newNick[0] == ':' {
		newNick = newNick[1:]
	}

	// Validate nickname
	if !isValidNickname(newNick) {
		c.SendNumeric(ERR_ERRONEUSNICKNAME, newNick+" :Erroneous nickname")
		return
	}

	// Check if nick is already in use
	if existing := c.server.GetClient(newNick); existing != nil && existing != c {
		c.SendNumeric(ERR_NICKNAMEINUSE, newNick+" :Nickname is already in use")
		return
	}

	oldNick := c.Nick()

	// If already registered, notify channels
	if c.IsRegistered() && oldNick != "" {
		// Create the message with the OLD prefix before changing the nick
		oldPrefix := fmt.Sprintf("%s!%s@%s", oldNick, c.User(), c.Host())
		message := fmt.Sprintf(":%s NICK :%s", oldPrefix, newNick)

		// Now change the nick
		c.SetNick(newNick)

		// Send to client themselves first (protocol-compliant NICK message)
		// The client ALWAYS gets their own NICK message - config only affects others
		c.SendMessage(message)

		// Then broadcast to other users in channels based on config
		for _, channel := range c.GetChannels() {
			// Get all clients in this channel
			for _, client := range channel.GetClients() {
				if client == c { // Skip self - already handled above
					continue
				}

				shouldSend := false
				if c.server.config.NickChangeNotification.ShowToEveryone {
					shouldSend = true
				} else if c.server.config.NickChangeNotification.ShowToOpers && client.IsOper() {
					shouldSend = true
				}

				if shouldSend {
					client.SendMessage(message)
				}
			}
		}

		// Send snomask notification for nick change
		if c.server != nil && oldNick != newNick {
			c.server.sendSnomask('n', fmt.Sprintf("Nick change: %s -> %s (%s@%s)",
				oldNick, newNick, c.User(), c.Host()))
		}
	} else {
		// Not registered yet, just change the nick
		c.SetNick(newNick)
	}

	c.checkRegistration()
}

// handleUser handles USER command
func (c *Client) handleUser(parts []string) {
	if len(parts) < 5 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "USER :Not enough parameters")
		return
	}

	if c.IsRegistered() {
		c.SendNumeric(ERR_ALREADYREGISTRED, ":You may not reregister")
		return
	}

	c.SetUser(parts[1])
	// parts[2] and parts[3] are ignored (mode and unused)
	realname := strings.Join(parts[4:], " ")
	if len(realname) > 0 && realname[0] == ':' {
		realname = realname[1:]
	}
	c.SetRealname(realname)

	c.checkRegistration()
}

// checkRegistration checks if client is ready to be registered
func (c *Client) checkRegistration() {
	// Don't complete registration if CAP negotiation is still in progress
	if c.capNegotiation {
		return
	}
	
	if !c.IsRegistered() && c.Nick() != "" && c.User() != "" {
		c.SetRegistered(true)
		c.sendWelcome()
	}
}

// sendWelcome sends welcome messages to newly registered client
func (c *Client) sendWelcome() {
	if c.server == nil {
		return
	}
	if c.server.config == nil {
		return
	}

	c.SendNumeric(RPL_WELCOME, fmt.Sprintf("Welcome to %s, %s", c.server.config.Server.Network, c.Prefix()))
	c.SendNumeric(RPL_YOURHOST, fmt.Sprintf("Your host is %s, running version %s", c.server.config.Server.Name, c.server.config.Server.Version))
	c.SendNumeric(RPL_CREATED, "This server was created recently")
	c.SendNumeric(RPL_MYINFO, fmt.Sprintf("%s %s iowsBGSx beIklmnpstqaohv", c.server.config.Server.Name, c.server.config.Server.Version))

	// Send ISUPPORT (005) messages to inform client about server capabilities
	c.SendNumeric(RPL_ISUPPORT, "PREFIX=(qaohv)~&@%+ CHANTYPES=# CHANMODES=beI,k,l,imnpst NETWORK="+c.server.config.Server.Network+" :are supported by this server")
	c.SendNumeric(RPL_ISUPPORT, "MAXCHANNELS="+fmt.Sprintf("%d", c.server.config.Limits.MaxChannels)+" NICKLEN="+fmt.Sprintf("%d", c.server.config.Limits.MaxNickLength)+" TOPICLEN="+fmt.Sprintf("%d", c.server.config.Limits.MaxTopicLength)+" :are supported by this server")
	c.SendNumeric(RPL_ISUPPORT, "MODES=20 STATUSMSG=~&@%+ EXCEPTS=e INVEX=I CASEMAPPING=rfc1459 CHANNELLEN=32 :are supported by this server")

	// Send user modes (221) - properly formatted
	userModes := c.GetModes()
	if userModes == "" {
		userModes = "+"
	} else if !strings.HasPrefix(userModes, "+") {
		userModes = "+" + userModes
	}
	c.SendNumeric(RPL_UMODEIS, userModes)

	// Send connection info (378) - shows real hostname
	c.SendNumeric(RPL_WHOISHOST, fmt.Sprintf("%s :is connecting from %s", c.Nick(), c.Host()))

	// Send LUSERS statistics
	c.server.mu.RLock()
	totalUsers := len(c.server.clients)
	totalChannels := len(c.server.channels)
	operCount := 0
	for _, client := range c.server.clients {
		if client.IsOper() {
			operCount++
		}
	}
	c.server.mu.RUnlock()

	c.SendNumeric(RPL_LUSERCLIENT, fmt.Sprintf("There are %d users and 0 services on 1 servers", totalUsers))
	if operCount > 0 {
		c.SendNumeric(RPL_LUSEROP, fmt.Sprintf("%d :operator(s) online", operCount))
	}
	c.SendNumeric(RPL_LUSERUNKNOWN, "0 :unknown connection(s)")
	if totalChannels > 0 {
		c.SendNumeric(RPL_LUSERCHANNELS, fmt.Sprintf("%d :channels formed", totalChannels))
	}
	c.SendNumeric(RPL_LUSERME, fmt.Sprintf("I have %d clients and 0 servers", totalUsers))

	// Send MOTD
	if len(c.server.config.MOTD) > 0 {
		c.SendNumeric(RPL_MOTDSTART, fmt.Sprintf("- %s Message of the Day -", c.server.config.Server.Name))
		for _, line := range c.server.config.MOTD {
			c.SendNumeric(RPL_MOTD, fmt.Sprintf("- %s", line))
		}
		c.SendNumeric(RPL_ENDOFMOTD, "End of /MOTD command")
	}

	// Send snomask notification for new client connection
	if c.server != nil {
		c.server.sendSnomask('c', fmt.Sprintf("Client connect: %s (%s@%s)",
			c.Nick(), c.User(), c.Host()))
	}
}

// handlePing handles PING command
func (c *Client) handlePing(parts []string) {
	if len(parts) < 2 {
		log.Printf("Invalid PING from %s: missing token", c.Nick())
		return
	}

	token := parts[1]
	if len(token) > 0 && token[0] == ':' {
		token = token[1:]
	}

	// Log PING received for debugging
	log.Printf("Received PING from client %s with token: %s", c.Nick(), token)

	// Determine the correct PONG format based on the ping token
	var pongMsg string

	if strings.HasPrefix(token, "LAG") {
		// HexChat LAG ping - use the exact format HexChat expects
		// HexChat expects: :servername PONG servername :LAGxxxxx
		pongMsg = fmt.Sprintf(":%s PONG %s :%s", c.server.config.Server.Name, c.server.config.Server.Name, token)
		log.Printf("Handling HexChat LAG ping with token: %s", token)
	} else if token == c.server.config.Server.Name {
		// Standard server ping - respond with server name
		pongMsg = fmt.Sprintf(":%s PONG %s :%s", c.server.config.Server.Name, c.server.config.Server.Name, token)
	} else {
		// Generic ping - echo back the token with colon prefix
		pongMsg = fmt.Sprintf(":%s PONG %s :%s", c.server.config.Server.Name, c.server.config.Server.Name, token)
	}

	log.Printf("Sending PONG to client %s: %s", c.Nick(), pongMsg)

	// Send the PONG response
	c.SendMessage(pongMsg)

	// Update ping tracking - treat any client PING as activity
	c.mu.Lock()
	c.lastPong = time.Now()
	c.lastActivity = time.Now() // Update activity time too
	c.waitingForPong = false
	c.mu.Unlock()

	log.Printf("Updated ping tracking for client %s", c.Nick())
}

// handlePong handles PONG command
func (c *Client) handlePong(parts []string) {
	// Update the last pong time for ping timeout tracking
	c.mu.Lock()
	c.lastPong = time.Now()
	c.lastActivity = time.Now() // Update activity time too
	c.waitingForPong = false
	c.mu.Unlock()

	// Log PONG receipt for debugging
	token := "unknown"
	if len(parts) > 1 {
		token = parts[1]
		if len(token) > 0 && token[0] == ':' {
			token = token[1:]
		}
	}

	log.Printf("Received PONG from client %s with token: %s", c.Nick(), token)
}

// handleJoin handles JOIN command
func (c *Client) handleJoin(parts []string) {
	log.Printf("JOIN command from %s (registered: %v): %v", c.Nick(), c.IsRegistered(), parts)

	if !c.IsRegistered() {
		c.SendNumeric(ERR_NOTREGISTERED, ":You have not registered")
		return
	}

	if len(parts) < 2 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "JOIN :Not enough parameters")
		return
	}

	channelNames := strings.Split(parts[1], ",")
	keys := []string{}
	if len(parts) > 2 {
		keys = strings.Split(parts[2], ",")
	}

	for i, channelName := range channelNames {
		if channelName == "0" {
			// Leave all channels
			for _, channel := range c.GetChannels() {
				c.handlePartChannel(channel.Name(), "Leaving all channels")
			}
			continue
		}

		if !isValidChannelName(channelName) {
			c.SendNumeric(ERR_NOSUCHCHANNEL, channelName+" :No such channel")
			continue
		}

		channel := c.server.GetOrCreateChannel(channelName)

		// Check if already in channel
		if c.IsInChannel(channelName) {
			continue
		}

		// Check channel modes and limits (God Mode can bypass all restrictions)
		key := ""
		if i < len(keys) {
			key = keys[i]
		}

		if !c.HasGodMode() {
			if channel.HasMode('k') && channel.Key() != key {
				c.SendNumeric(ERR_BADCHANNELKEY, channelName+" :Cannot join channel (+k)")
				continue
			}

			if channel.HasMode('l') && channel.UserCount() >= channel.Limit() {
				c.SendNumeric(ERR_CHANNELISFULL, channelName+" :Cannot join channel (+l)")
				continue
			}

			// Check for bans (God Mode bypasses bans)
			if channel.IsBanned(c) {
				c.SendNumeric(ERR_BANNEDFROMCHAN, channelName+" :Cannot join channel (+b)")
				continue
			}

			// Check invite-only mode (God Mode bypasses invite requirement)
			if channel.HasMode('i') && !channel.IsInvited(c) {
				c.SendNumeric(ERR_INVITEONLYCHAN, channelName+" :Cannot join channel (+i)")
				continue
			}
		} else {
			// God Mode user joining - notify operators
			c.sendSnomask('o', fmt.Sprintf("GOD MODE: %s bypassed restrictions to join %s", c.Nick(), channelName))
		}

		// Check if user is already in the channel
		if c.IsInChannel(channelName) {
			// User is already in the channel, skip
			continue
		}

		// Join the channel
		channel.AddClient(c)
		c.AddChannel(channel)

		message := fmt.Sprintf(":%s JOIN :%s", c.Prefix(), channelName)

		// Send JOIN to the client themselves first
		c.SendMessage(message)

		// Then broadcast to others in the channel
		channel.Broadcast(message, c)

		// Send topic if exists
		if channel.Topic() != "" {
			c.SendNumeric(RPL_TOPIC, channelName+" :"+channel.Topic())
			c.SendNumeric(RPL_TOPICWHOTIME, fmt.Sprintf("%s %s %d", channelName, channel.TopicBy(), channel.TopicTime().Unix()))
		}

		// Send names list
		c.sendNames(channel)
	}
}

// handlePart handles PART command
func (c *Client) handlePart(parts []string) {
	if !c.IsRegistered() {
		c.SendNumeric(ERR_NOTREGISTERED, ":You have not registered")
		return
	}

	if len(parts) < 2 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "PART :Not enough parameters")
		return
	}

	channelNames := strings.Split(parts[1], ",")
	reason := "Leaving"
	if len(parts) > 2 {
		reason = strings.Join(parts[2:], " ")
		if len(reason) > 0 && reason[0] == ':' {
			reason = reason[1:]
		}
	}

	for _, channelName := range channelNames {
		c.handlePartChannel(channelName, reason)
	}
}

func (c *Client) handlePartChannel(channelName, reason string) {
	if !c.IsInChannel(channelName) {
		c.SendNumeric(ERR_NOTONCHANNEL, channelName+" :You're not on that channel")
		return
	}

	channel := c.server.GetChannel(channelName)
	if channel == nil {
		return
	}

	message := fmt.Sprintf(":%s PART %s :%s", c.Prefix(), channelName, reason)
	channel.Broadcast(message, nil)

	channel.RemoveClient(c)
	c.RemoveChannel(channelName)

	// Remove empty channel
	if channel.UserCount() == 0 {
		c.server.RemoveChannel(channelName)
	}
}

// handlePrivmsg handles PRIVMSG command with IRCv3 support
func (c *Client) handlePrivmsg(parts []string) {
	if !c.IsRegistered() {
		c.SendNumeric(ERR_NOTREGISTERED, ":You have not registered")
		return
	}

	if len(parts) < 2 {
		c.SendNumeric(ERR_NORECIPIENT, ":No recipient given (PRIVMSG)")
		return
	}

	if len(parts) < 3 {
		c.SendNumeric(ERR_NOTEXTTOSEND, ":No text to send")
		return
	}

	target := parts[1]
	message := strings.Join(parts[2:], " ")
	if len(message) > 0 && message[0] == ':' {
		message = message[1:]
	}

	// Create IRCv3 message
	ircMsg := &IRCMessage{
		Tags:    make(map[string]string),
		Prefix:  c.Prefix(),
		Command: "PRIVMSG",
		Params:  []string{target, ":" + message},
	}

	// Add IRCv3 tags
	c.AddServerTime(ircMsg)
	c.AddAccountTag(ircMsg)

	if isChannelName(target) {
		// Channel message
		channel := c.server.GetChannel(target)
		if channel == nil {
			c.SendNumeric(ERR_NOSUCHCHANNEL, target+" :No such channel")
			return
		}

		if !c.IsInChannel(target) {
			c.SendNumeric(ERR_CANNOTSENDTOCHAN, target+" :Cannot send to channel")
			return
		}

		// Check if user is quieted first (takes priority over moderated check)
		if channel.IsQuieted(c) {
			// Check if user has privilege to speak despite being quieted
			if !channel.IsOwner(c) && !channel.IsOperator(c) && !channel.IsHalfop(c) {
				c.SendNumeric(ERR_QUIETED, target+" :Cannot send to channel (you are quieted)")
				return
			}
		}

		// Check if channel is moderated and user lacks privileges
		if channel.HasMode('m') && !channel.CanSendMessage(c) {
			c.SendNumeric(ERR_MODERATED, target+" :Cannot send to channel (channel is moderated)")
			return
		}

		// Broadcast with IRCv3 support
		c.broadcastToChannel(channel, ircMsg)

		// Echo message back to sender if they have echo-message capability
		// TEMPORARILY DISABLED TO FIX DUPLICATION ISSUE
		// if c.HasCapability("echo-message") {
		// 	c.SendMessage(ircMsg.FormatMessage())
		// }

	} else {
		// Private message
		targetClient := c.server.GetClient(target)
		if targetClient == nil {
			c.SendNumeric(ERR_NOSUCHNICK, target+" :No such nick/channel")
			return
		}

		if targetClient.Away() != "" {
			c.SendNumeric(RPL_AWAY, fmt.Sprintf("%s :%s", target, targetClient.Away()))
		}

		// Send IRCv3 message to target
		targetClient.SendMessage(ircMsg.FormatMessage())

		// Echo message back to sender if they have echo-message capability
		// TEMPORARILY DISABLED TO FIX DUPLICATION ISSUE
		// if c.HasCapability("echo-message") {
		// 	c.SendMessage(ircMsg.FormatMessage())
		// }
	}
}

// handleNotice handles NOTICE command
func (c *Client) handleNotice(parts []string) {
	if !c.IsRegistered() {
		return // NOTICE should not generate error responses
	}

	if len(parts) < 3 {
		return
	}

	target := parts[1]
	message := strings.Join(parts[2:], " ")
	if len(message) > 0 && message[0] == ':' {
		message = message[1:]
	}

	if isChannelName(target) {
		// Channel notice
		channel := c.server.GetChannel(target)
		if channel == nil || !c.IsInChannel(target) {
			return
		}

		msg := fmt.Sprintf(":%s NOTICE %s :%s", c.Prefix(), target, message)
		channel.Broadcast(msg, c)
	} else {
		// Private notice
		targetClient := c.server.GetClient(target)
		if targetClient == nil {
			return
		}

		msg := fmt.Sprintf(":%s NOTICE %s :%s", c.Prefix(), target, message)
		targetClient.SendMessage(msg)
	}
}

// handleTagmsg handles TAGMSG command (IRCv3.2)
// TAGMSG is used for tag-only messages like typing indicators, reactions, etc.
func (c *Client) handleTagmsg(parts []string, tags map[string]string) {
	if !c.IsRegistered() {
		c.SendNumeric(ERR_NOTREGISTERED, ":You have not registered")
		return
	}

	if len(parts) < 2 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "TAGMSG :Not enough parameters")
		return
	}

	target := parts[1]

	// Build tag string
	var tagString string
	if len(tags) > 0 {
		var tagPairs []string
		for key, value := range tags {
			if value == "" {
				tagPairs = append(tagPairs, key)
			} else {
				tagPairs = append(tagPairs, fmt.Sprintf("%s=%s", key, value))
			}
		}
		tagString = "@" + strings.Join(tagPairs, ";") + " "
	}

	if isChannelName(target) {
		// Channel tagmsg
		channel := c.server.GetChannel(target)
		if channel == nil || !c.IsInChannel(target) {
			c.SendNumeric(ERR_NOTONCHANNEL, target+" :You're not on that channel")
			return
		}

		msg := fmt.Sprintf("%s:%s TAGMSG %s", tagString, c.Prefix(), target)
		channel.Broadcast(msg, c)
	} else {
		// Private tagmsg
		targetClient := c.server.GetClient(target)
		if targetClient == nil {
			c.SendNumeric(ERR_NOSUCHNICK, target+" :No such nick/channel")
			return
		}

		msg := fmt.Sprintf("%s:%s TAGMSG %s", tagString, c.Prefix(), target)
		targetClient.SendMessage(msg)
	}
}

// handleWho handles WHO command
func (c *Client) handleWho(parts []string) {
	if !c.IsRegistered() {
		c.SendNumeric(ERR_NOTREGISTERED, ":You have not registered")
		return
	}

	if len(parts) < 2 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "WHO :Not enough parameters")
		return
	}

	target := parts[1]

	if isChannelName(target) {
		channel := c.server.GetChannel(target)
		if channel == nil {
			c.SendNumeric(ERR_NOSUCHCHANNEL, target+" :No such channel")
			return
		}

		for _, client := range channel.GetClients() {
			// Skip stealth mode users unless requester is an operator
			if !client.IsVisibleTo(c) {
				continue
			}

			flags := ""
			if client.IsOper() {
				flags += "*"
			}
			if client.Away() != "" {
				flags += "G"
			} else {
				flags += "H"
			}

			// Add channel status flags in order of hierarchy
			if channel.IsOwner(client) {
				flags += "~"
			} else if channel.IsAdmin(client) {
				flags += "&"
			} else if channel.IsOperator(client) {
				flags += "@"
			} else if channel.IsHalfop(client) {
				flags += "%"
			} else if channel.IsVoice(client) {
				flags += "+"
			}

			c.SendNumeric(352, fmt.Sprintf("%s %s %s %s %s %s :0 %s",
				target, client.User(), client.HostForUser(c), c.server.config.Server.Name,
				client.Nick(), flags, client.Realname()))
		}
	}

	c.SendNumeric(315, target+" :End of /WHO list")
}

// handleWhois handles WHOIS command
func (c *Client) handleWhois(parts []string) {
	if !c.IsRegistered() {
		c.SendNumeric(ERR_NOTREGISTERED, ":You have not registered")
		return
	}

	if len(parts) < 2 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "WHOIS :Not enough parameters")
		return
	}

	nick := parts[1]
	target := c.server.GetClient(nick)
	if target == nil {
		c.SendNumeric(ERR_NOSUCHNICK, nick+" :No such nick")
		return
	}

	// Basic user information (always shown)
	hostname := target.HostForUser(c)

	// Always show the public hostname in RPL_WHOISUSER
	c.SendNumeric(RPL_WHOISUSER, fmt.Sprintf("%s %s %s * :%s",
		target.Nick(), target.User(), hostname, target.Realname()))

	// Show real host if configured and permitted
	if c.canSeeWhoisInfo(target, "real_host") {
		realHost := target.Host()
		if hostname != realHost {
			// Show real host if different from displayed host
			c.SendNumeric(RPL_WHOISHOST, fmt.Sprintf("%s :is connecting from %s",
				target.Nick(), realHost))
		} else {
			// Even if same, show real IP for debugging
			c.SendNumeric(RPL_WHOISHOST, fmt.Sprintf("%s :is connecting from %s (real IP)",
				target.Nick(), realHost))
		}
	}

	// Server information
	c.SendNumeric(RPL_WHOISSERVER, fmt.Sprintf("%s %s :%s",
		target.Nick(), c.server.config.Server.Name, c.server.config.Server.Description))

	// Operator status
	if target.IsOper() {
		c.SendNumeric(RPL_WHOISOPERATOR, target.Nick()+" :is an IRC operator")

		// Show operator class if configured
		if c.canSeeWhoisInfo(target, "oper_class") {
			operClass := target.OperClass()
			if operClass != "" {
				// Load operator config to get class description and rank name
				operConfig, err := LoadOperConfig(c.server.config.OperConfig.ConfigFile)
				if err == nil && c.server.config.OperConfig.Enable {
					class := operConfig.GetOperClass(operClass)
					if class != nil {
						rankName := operConfig.GetRankName(class.Rank)
						c.SendMessage(fmt.Sprintf(":%s 313 %s %s :is an IRC operator (%s - %s) [%s]",
							c.server.config.Server.Name, c.Nick(), target.Nick(), class.Name, class.Description, rankName))
					} else {
						c.SendMessage(fmt.Sprintf(":%s 313 %s %s :is an IRC operator (class: %s)",
							c.server.config.Server.Name, c.Nick(), target.Nick(), operClass))
					}
				} else {
					c.SendMessage(fmt.Sprintf(":%s 313 %s %s :is an IRC operator (class: %s)",
						c.server.config.Server.Name, c.Nick(), target.Nick(), operClass))
				}
			}
		}
	}

	// Away status
	if target.Away() != "" {
		c.SendNumeric(RPL_AWAY, fmt.Sprintf("%s :%s", target.Nick(), target.Away()))
	}

	// User modes
	if c.canSeeWhoisInfo(target, "user_modes") {
		modes := target.GetModes()
		if modes != "" {
			c.SendMessage(fmt.Sprintf(":%s 379 %s %s :is using modes %s",
				c.server.config.Server.Name, c.Nick(), target.Nick(), modes))
		}
	}

	// SSL status
	if c.canSeeWhoisInfo(target, "ssl_status") && target.IsSSL() {
		c.SendMessage(fmt.Sprintf(":%s 671 %s %s :is using a secure connection",
			c.server.config.Server.Name, c.Nick(), target.Nick()))
	}

	// Channels
	if c.canSeeChannels(target) {
		var channels []string
		config := c.server.config.WhoisFeatures.ShowChannels

		for _, channel := range target.GetChannels() {
			// Skip secret/private channels based on config
			if config.HideSecret && channel.HasMode('s') && !channel.HasClient(c) {
				continue
			}
			if config.HidePrivate && channel.HasMode('p') && !channel.HasClient(c) {
				continue
			}

			channelName := channel.Name()
			if config.ShowMembership {
				if channel.IsOperator(target) {
					channelName = "@" + channelName
				} else if channel.IsVoice(target) {
					channelName = "+" + channelName
				}
			}
			channels = append(channels, channelName)
		}

		if len(channels) > 0 {
			c.SendNumeric(RPL_WHOISCHANNELS, fmt.Sprintf("%s :%s", target.Nick(), strings.Join(channels, " ")))
		}
	}

	// Idle time
	if c.canSeeWhoisInfo(target, "idle_time") {
		idleTime := int(time.Since(target.LastActivity()).Seconds())
		c.SendNumeric(RPL_WHOISIDLE, fmt.Sprintf("%s %d %d :seconds idle, signon time",
			target.Nick(), idleTime, target.ConnectTime().Unix()))
	}

	// Signon time (alternative if idle time is not shown)
	if !c.canSeeWhoisInfo(target, "idle_time") && c.canSeeWhoisInfo(target, "signon_time") {
		c.SendMessage(fmt.Sprintf(":%s 317 %s %s :signed on %s",
			c.server.config.Server.Name, c.Nick(), target.Nick(),
			target.ConnectTime().Format("Mon Jan 2 15:04:05 2006")))
	}

	// Account name (for services integration)
	if c.canSeeWhoisInfo(target, "account_name") && target.Account() != "" {
		c.SendMessage(fmt.Sprintf(":%s 330 %s %s %s :is logged in as",
			c.server.config.Server.Name, c.Nick(), target.Nick(), target.Account()))
	}

	// Client information
	if c.canSeeWhoisInfo(target, "client_info") {
		c.SendMessage(fmt.Sprintf(":%s 351 %s %s :is using client TechIRCd-Client",
			c.server.config.Server.Name, c.Nick(), target.Nick()))
	}

	c.SendNumeric(RPL_ENDOFWHOIS, target.Nick()+" :End of /WHOIS list")
}

// handleNames handles NAMES command
func (c *Client) handleNames(parts []string) {
	if !c.IsRegistered() {
		c.SendNumeric(ERR_NOTREGISTERED, ":You have not registered")
		return
	}

	if len(parts) < 2 {
		// Send names for all channels
		for _, channel := range c.server.GetChannels() {
			if c.IsInChannel(channel.Name()) {
				c.sendNames(channel)
			}
		}
		return
	}

	channelNames := strings.Split(parts[1], ",")
	for _, channelName := range channelNames {
		channel := c.server.GetChannel(channelName)
		if channel != nil && c.IsInChannel(channelName) {
			c.sendNames(channel)
		}
	}
}

func (c *Client) sendNames(channel *Channel) {
	var names []string
	for _, client := range channel.GetClients() {
		// Skip stealth mode users unless requester is an operator
		if !client.IsVisibleTo(c) {
			continue
		}

		name := client.Nick()
		var prefixes string

		// Build prefixes in order of hierarchy: owner > admin > operator > halfop > voice
		if channel.IsOwner(client) {
			prefixes += "~"
		}
		if channel.IsAdmin(client) {
			prefixes += "&"
		}
		if channel.IsOperator(client) {
			prefixes += "@"
		}
		if channel.IsHalfop(client) {
			prefixes += "%"
		}
		if channel.IsVoice(client) {
			prefixes += "+"
		}

		// For multi-prefix clients, show all prefixes. For others, show only the highest
		if c.HasCapability("multi-prefix") {
			name = prefixes + name
		} else {
			// Show only the highest prefix for non-multi-prefix clients
			if len(prefixes) > 0 {
				name = string(prefixes[0]) + name
			}
		}
		names = append(names, name)
	}

	symbol := "="
	if channel.HasMode('s') {
		symbol = "@"
	} else if channel.HasMode('p') {
		symbol = "*"
	}

	c.SendNumeric(RPL_NAMREPLY, fmt.Sprintf("%s %s :%s", symbol, channel.Name(), strings.Join(names, " ")))
	c.SendNumeric(RPL_ENDOFNAMES, channel.Name()+" :End of /NAMES list")
}

// handleQuit handles QUIT command
func (c *Client) handleQuit(parts []string) {
	reason := "Client quit"
	if len(parts) > 1 {
		reason = strings.Join(parts[1:], " ")
		if len(reason) > 0 && reason[0] == ':' {
			reason = reason[1:]
		}
	}

	// Broadcast QUIT message to all channels the client is in
	quitMessage := fmt.Sprintf(":%s QUIT :%s", c.Prefix(), reason)
	for _, channel := range c.GetChannels() {
		// Send QUIT message to all users in the channel
		for _, client := range channel.GetClients() {
			if client != c { // Don't send to the quitting client
				client.SendMessage(quitMessage)
			}
		}
		// Remove client from channel
		channel.RemoveClient(c)
		// Remove empty channels
		if len(channel.GetClients()) == 0 && c.server != nil {
			c.server.RemoveChannel(channel.name)
		}
	}

	// Remove client from server
	c.server.RemoveClient(c)

	// Close the connection
	c.conn.Close()
}

// handleMode handles MODE command
func (c *Client) handleMode(parts []string) {
	if len(parts) < 2 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "MODE :Not enough parameters")
		return
	}

	target := parts[1]

	// Handle user mode requests
	if !isChannelName(target) {
		if target != c.Nick() {
			c.SendNumeric(ERR_USERSDONTMATCH, ":Cannot change mode for other users")
			return
		}

		// If no mode changes specified, return current user modes
		if len(parts) == 2 {
			modes := c.GetModes()
			if modes == "" {
				modes = "+"
			}
			c.SendNumeric(RPL_UMODEIS, modes)
			return
		}

		// Parse user mode changes
		modeString := parts[2]
		adding := true
		var appliedModes []string

		for _, char := range modeString {
			switch char {
			case '+':
				adding = true
			case '-':
				adding = false
			case 'i': // invisible
				c.SetMode('i', adding)
				if adding {
					appliedModes = append(appliedModes, "+i")
				} else {
					appliedModes = append(appliedModes, "-i")
				}
			case 'w': // wallops
				c.SetMode('w', adding)
				if adding {
					appliedModes = append(appliedModes, "+w")
				} else {
					appliedModes = append(appliedModes, "-w")
				}
			case 's': // server notices (requires oper)
				if !c.IsOper() && adding {
					continue // silently ignore for non-opers
				}
				c.SetMode('s', adding)
				if adding {
					appliedModes = append(appliedModes, "+s")
				} else {
					appliedModes = append(appliedModes, "-s")
				}
			case 'o': // operator (cannot be set manually)
				if adding {
					c.SendNumeric(ERR_UMODEUNKNOWNFLAG, ":Unknown MODE flag")
				} else {
					// Allow de-opering
					c.SetOper(false)
					c.SetMode('o', false)
					appliedModes = append(appliedModes, "-o")
					// Clear snomasks when de-opering
					c.snomasks = make(map[rune]bool)
					c.sendSnomask('o', fmt.Sprintf("%s is no longer an IRC operator", c.Nick()))
				}
			case 'r': // registered (cannot be set manually, services only)
				c.SendNumeric(ERR_UMODEUNKNOWNFLAG, ":Unknown MODE flag")
			case 'x': // host masking (TechIRCd special)
				c.SetMode('x', adding)
				if adding {
					appliedModes = append(appliedModes, "+x")
					// TODO: Implement host masking
				} else {
					appliedModes = append(appliedModes, "-x")
				}
			case 'z': // SSL/TLS (automatic, cannot be manually set)
				if c.IsSSL() {
					c.SetMode('z', true)
				}
				// Ignore attempts to manually set/unset
			case 'B': // bot flag (TechIRCd special)
				c.SetMode('B', adding)
				if adding {
					appliedModes = append(appliedModes, "+B")
				} else {
					appliedModes = append(appliedModes, "-B")
				}
			case 'G': // God Mode (requires oper and god_mode permission)
				if !c.IsOper() {
					c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied- You're not an IRC operator")
					continue
				}
				if !c.HasOperPermission("god_mode") {
					c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied - You need god_mode permission")
					continue
				}
				// Only change mode if it's different from current state
				if c.HasMode('G') != adding {
					c.SetMode('G', adding)
					if adding {
						appliedModes = append(appliedModes, "+G")
						c.SendMessage(fmt.Sprintf(":%s NOTICE %s :*** GOD MODE enabled - You have ultimate power!",
							c.server.config.Server.Name, c.Nick()))
						c.sendSnomask('o', fmt.Sprintf("%s has enabled GOD MODE - Ultimate channel override powers active", c.Nick()))
					} else {
						appliedModes = append(appliedModes, "-G")
						c.SendMessage(fmt.Sprintf(":%s NOTICE %s :*** GOD MODE disabled",
							c.server.config.Server.Name, c.Nick()))
						c.sendSnomask('o', fmt.Sprintf("%s has disabled GOD MODE", c.Nick()))
					}
				}
			case 'S': // Stealth Mode (requires oper and stealth_mode permission)
				if !c.IsOper() {
					c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied- You're not an IRC operator")
					continue
				}
				if !c.HasOperPermission("stealth_mode") {
					c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied - You need stealth_mode permission")
					continue
				}
				// Only change mode if it's different from current state
				if c.HasMode('S') != adding {
					c.SetMode('S', adding)
					if adding {
						appliedModes = append(appliedModes, "+S")
						c.SendMessage(fmt.Sprintf(":%s NOTICE %s :*** STEALTH MODE enabled - You are now invisible to users",
							c.server.config.Server.Name, c.Nick()))
						c.sendSnomask('o', fmt.Sprintf("%s has enabled STEALTH MODE - Now invisible to regular users", c.Nick()))
					} else {
						appliedModes = append(appliedModes, "-S")
						c.SendMessage(fmt.Sprintf(":%s NOTICE %s :*** STEALTH MODE disabled - You are now visible",
							c.server.config.Server.Name, c.Nick()))
						c.sendSnomask('o', fmt.Sprintf("%s has disabled STEALTH MODE", c.Nick()))
					}
				}
			default:
				c.SendNumeric(ERR_UMODEUNKNOWNFLAG, ":Unknown MODE flag")
			}
		}

		// Send mode changes back to user
		if len(appliedModes) > 0 {
			modeStr := strings.Join(appliedModes, "")
			c.SendMessage(fmt.Sprintf(":%s MODE %s :%s", c.Nick(), c.Nick(), modeStr))
		}
		return
	}

	// Handle channel mode requests
	channel := c.server.GetChannel(target)
	if channel == nil {
		c.SendNumeric(ERR_NOSUCHCHANNEL, target+" :No such channel")
		return
	}

	if !c.IsInChannel(target) {
		c.SendNumeric(ERR_NOTONCHANNEL, target+" :You're not on that channel")
		return
	}

	// If no mode changes specified, return current channel modes
	if len(parts) == 2 {
		modes := channel.GetModes()
		if modes == "" {
			modes = "+"
		}
		c.SendNumeric(RPL_CHANNELMODEIS, fmt.Sprintf("%s %s", target, modes))
		return
	}

	// Parse mode changes
	modeString := parts[2]
	args := parts[3:]
	argIndex := 0

	// Check if user has operator privileges (required for most mode changes)
	// God Mode users can bypass operator requirement
	if !c.HasGodMode() && !channel.IsOwner(c) && !channel.IsOperator(c) && !channel.IsHalfop(c) {
		c.SendNumeric(ERR_CHANOPRIVSNEEDED, target+" :You're not channel operator")
		return
	}

	// If using God Mode to set modes, notify operators
	if c.HasGodMode() && !channel.IsOwner(c) && !channel.IsOperator(c) && !channel.IsHalfop(c) {
		c.sendSnomask('o', fmt.Sprintf("GOD MODE: %s set modes on %s without operator privileges", c.Nick(), target))
	}

	adding := true
	var appliedModes []string
	var appliedArgs []string

	for _, char := range modeString {
		switch char {
		case '+':
			adding = true
		case '-':
			adding = false
		case 'o': // operator
			if argIndex >= len(args) {
				continue
			}
			targetNick := args[argIndex]
			argIndex++

			// Check if operator mode is allowed in config
			if !c.server.config.Channels.AllowedModes.Operator {
				c.SendNumeric(ERR_UNKNOWNMODE, "+o :Operator mode is disabled")
				continue
			}

			targetClient := c.server.GetClient(targetNick)
			if targetClient == nil {
				c.SendNumeric(ERR_NOSUCHNICK, targetNick+" :No such nick/channel")
				continue
			}

			if !targetClient.IsInChannel(target) {
				c.SendNumeric(ERR_USERNOTINCHANNEL, fmt.Sprintf("%s %s :They aren't on that channel", targetNick, target))
				continue
			}

			// Check if the user already has the operator status we're trying to set
			currentlyOp := channel.IsOperator(targetClient)
			if adding && currentlyOp {
				// Already an operator, skip this change
				continue
			}
			if !adding && !currentlyOp {
				// Already not an operator, skip this change
				continue
			}

			channel.SetOperator(targetClient, adding)
			if adding {
				appliedModes = append(appliedModes, "+o")
			} else {
				appliedModes = append(appliedModes, "-o")
			}
			appliedArgs = append(appliedArgs, targetNick)

		case 'v': // voice
			if argIndex >= len(args) {
				continue
			}
			targetNick := args[argIndex]
			argIndex++

			// Check if voice mode is allowed in config
			if !c.server.config.Channels.AllowedModes.Voice {
				c.SendNumeric(ERR_UNKNOWNMODE, "+v :Voice mode is disabled")
				continue
			}

			targetClient := c.server.GetClient(targetNick)
			if targetClient == nil {
				c.SendNumeric(ERR_NOSUCHNICK, targetNick+" :No such nick/channel")
				continue
			}

			if !targetClient.IsInChannel(target) {
				c.SendNumeric(ERR_USERNOTINCHANNEL, fmt.Sprintf("%s %s :They aren't on that channel", targetNick, target))
				continue
			}

			// Check if the user already has the voice status we're trying to set
			currentlyVoiced := channel.IsVoice(targetClient)
			if adding && currentlyVoiced {
				// Already has voice, skip this change
				continue
			}
			if !adding && !currentlyVoiced {
				// Already doesn't have voice, skip this change
				continue
			}

			channel.SetVoice(targetClient, adding)
			if adding {
				appliedModes = append(appliedModes, "+v")
			} else {
				appliedModes = append(appliedModes, "-v")
			}
			appliedArgs = append(appliedArgs, targetNick)

		case 'h': // halfop
			if argIndex >= len(args) {
				continue
			}
			targetNick := args[argIndex]
			argIndex++

			// Check if halfop mode is allowed in config
			if !c.server.config.Channels.AllowedModes.Halfop {
				c.SendNumeric(ERR_UNKNOWNMODE, "+h :Halfop mode is disabled")
				continue
			}

			targetClient := c.server.GetClient(targetNick)
			if targetClient == nil {
				c.SendNumeric(ERR_NOSUCHNICK, targetNick+" :No such nick/channel")
				continue
			}

			if !targetClient.IsInChannel(target) {
				c.SendNumeric(ERR_USERNOTINCHANNEL, fmt.Sprintf("%s %s :They aren't on that channel", targetNick, target))
				continue
			}

			// Check if the user already has the halfop status we're trying to set
			currentlyHalfop := channel.IsHalfop(targetClient)
			if adding && currentlyHalfop {
				// Already has halfop, skip this change
				continue
			}
			if !adding && !currentlyHalfop {
				// Already doesn't have halfop, skip this change
				continue
			}

			channel.SetHalfop(targetClient, adding)
			if adding {
				appliedModes = append(appliedModes, "+h")
			} else {
				appliedModes = append(appliedModes, "-h")
			}
			appliedArgs = append(appliedArgs, targetNick)

		case 'q': // owner/founder
			if argIndex >= len(args) {
				continue
			}
			targetNick := args[argIndex]
			argIndex++

			// Check if owner mode is allowed in config
			if !c.server.config.Channels.AllowedModes.Owner {
				c.SendNumeric(ERR_UNKNOWNMODE, "+q :Owner mode is disabled")
				continue
			}

			// Only existing owners can grant/remove owner status, or God Mode users
			if !channel.IsOwner(c) && !c.HasGodMode() {
				c.SendNumeric(ERR_CHANOPRIVSNEEDED, target+" :You're not channel owner")
				continue
			}

			targetClient := c.server.GetClient(targetNick)
			if targetClient == nil {
				c.SendNumeric(ERR_NOSUCHNICK, targetNick+" :No such nick/channel")
				continue
			}

			if !targetClient.IsInChannel(target) {
				c.SendNumeric(ERR_USERNOTINCHANNEL, fmt.Sprintf("%s %s :They aren't on that channel", targetNick, target))
				continue
			}

			// Check if the user already has the owner status we're trying to set
			currentlyOwner := channel.IsOwner(targetClient)
			if adding && currentlyOwner {
				// Already has owner, skip this change
				continue
			}
			if !adding && !currentlyOwner {
				// Already doesn't have owner, skip this change
				continue
			}

			channel.SetOwner(targetClient, adding)
			if adding {
				appliedModes = append(appliedModes, "+q")
			} else {
				appliedModes = append(appliedModes, "-q")
			}
			appliedArgs = append(appliedArgs, targetNick)

		case 'a': // admin
			if argIndex >= len(args) {
				continue
			}
			targetNick := args[argIndex]
			argIndex++

			// Check if admin mode is allowed in config
			if !c.server.config.Channels.AllowedModes.Admin {
				c.SendNumeric(ERR_UNKNOWNMODE, "+a :Admin mode is disabled")
				continue
			}

			// Only existing owners/admins can grant/remove admin status, or God Mode users
			if !channel.IsOwner(c) && !channel.IsAdmin(c) && !c.HasGodMode() {
				c.SendNumeric(ERR_CHANOPRIVSNEEDED, target+" :You need admin or owner privileges")
				continue
			}

			targetClient := c.server.GetClient(targetNick)
			if targetClient == nil {
				c.SendNumeric(ERR_NOSUCHNICK, targetNick+" :No such nick/channel")
				continue
			}

			if !targetClient.IsInChannel(target) {
				c.SendNumeric(ERR_USERNOTINCHANNEL, fmt.Sprintf("%s %s :They aren't on that channel", targetNick, target))
				continue
			}

			// Check if the user already has the admin status we're trying to set
			currentlyAdmin := channel.IsAdmin(targetClient)
			if adding && currentlyAdmin {
				// Already has admin, skip this change
				continue
			}
			if !adding && !currentlyAdmin {
				// Already doesn't have admin, skip this change
				continue
			}

			channel.SetAdmin(targetClient, adding)
			if adding {
				appliedModes = append(appliedModes, "+a")
			} else {
				appliedModes = append(appliedModes, "-a")
			}
			appliedArgs = append(appliedArgs, targetNick)

		case 'm': // moderated
			channel.SetMode('m', adding)
			if adding {
				appliedModes = append(appliedModes, "+m")
			} else {
				appliedModes = append(appliedModes, "-m")
			}

		case 'n': // no external messages
			channel.SetMode('n', adding)
			if adding {
				appliedModes = append(appliedModes, "+n")
			} else {
				appliedModes = append(appliedModes, "-n")
			}

		case 't': // topic restriction
			channel.SetMode('t', adding)
			if adding {
				appliedModes = append(appliedModes, "+t")
			} else {
				appliedModes = append(appliedModes, "-t")
			}

		case 'i': // invite only
			channel.SetMode('i', adding)
			if adding {
				appliedModes = append(appliedModes, "+i")
			} else {
				appliedModes = append(appliedModes, "-i")
			}

		case 's': // secret
			channel.SetMode('s', adding)
			if adding {
				appliedModes = append(appliedModes, "+s")
			} else {
				appliedModes = append(appliedModes, "-s")
			}

		case 'p': // private
			channel.SetMode('p', adding)
			if adding {
				appliedModes = append(appliedModes, "+p")
			} else {
				appliedModes = append(appliedModes, "-p")
			}

		case 'k': // key (password)
			if adding {
				if argIndex >= len(args) {
					continue
				}
				key := args[argIndex]
				argIndex++
				channel.SetKey(key)
				channel.SetMode('k', true)
				appliedModes = append(appliedModes, "+k")
				appliedArgs = append(appliedArgs, key)
			} else {
				channel.SetKey("")
				channel.SetMode('k', false)
				appliedModes = append(appliedModes, "-k")
			}

		case 'l': // limit
			if adding {
				if argIndex >= len(args) {
					continue
				}
				limitStr := args[argIndex]
				argIndex++
				// Parse limit (simplified - should validate it's a number)
				limit := 0
				fmt.Sscanf(limitStr, "%d", &limit)
				if limit > 0 {
					channel.SetLimit(limit)
					channel.SetMode('l', true)
					appliedModes = append(appliedModes, "+l")
					appliedArgs = append(appliedArgs, limitStr)
				}
			} else {
				channel.SetLimit(0)
				channel.SetMode('l', false)
				appliedModes = append(appliedModes, "-l")
			}

		case 'b': // ban (enhanced with extended ban types)
			var mask string
			if argIndex >= len(args) {
				if adding {
					// If no mask provided and we're setting +b, generate user's hostmask
					mask = fmt.Sprintf("%s!%s@%s", c.Nick(), c.User(), c.Host())
				} else {
					// List bans - send ban list to client
					bans := channel.GetBans()
					for _, ban := range bans {
						// RPL_BANLIST: 367 <channel> <banmask> <who> <when>
						c.SendNumeric(RPL_BANLIST, fmt.Sprintf("%s %s %s %d", target, ban, "server", time.Now().Unix()))
					}
					// Also list quiet bans with ~q: prefix
					for _, quiet := range channel.quietList {
						c.SendNumeric(RPL_BANLIST, fmt.Sprintf("%s ~q:%s %s %d", target, quiet, "server", time.Now().Unix()))
					}
					// RPL_ENDOFBANLIST: 368 <channel> :End of channel ban list
					c.SendNumeric(RPL_ENDOFBANLIST, target+" :End of channel ban list")
					continue
				}
			} else {
				mask = args[argIndex]
				argIndex++

				// Expand partial masks to full hostmask format
				mask = expandBanMask(mask)
			}

			// Check for extended ban types (e.g., ~q:nick!user@host for quiet)
			if strings.HasPrefix(mask, "~") && len(mask) > 2 && mask[2] == ':' {
				banType := mask[1]  // The character after ~
				banMask := mask[3:] // The mask after ~x:

				switch banType {
				case 'q': // Quiet ban
					if adding {
						// Add to quiet list
						channel.quietList = append(channel.quietList, banMask)
						appliedModes = append(appliedModes, "+b")
						appliedArgs = append(appliedArgs, mask)

						// Send snomask to opers
						if c.IsOper() {
							c.server.sendSnomask('x', fmt.Sprintf("%s set quiet ban %s on %s", c.Nick(), banMask, target))
						}
					} else {
						// Remove from quiet list
						for i, quiet := range channel.quietList {
							if quiet == banMask {
								channel.quietList = append(channel.quietList[:i], channel.quietList[i+1:]...)
								appliedModes = append(appliedModes, "-b")
								appliedArgs = append(appliedArgs, mask)

								// Send snomask to opers
								if c.IsOper() {
									c.server.sendSnomask('x', fmt.Sprintf("%s removed quiet ban %s on %s", c.Nick(), banMask, target))
								}
								break
							}
						}
					}
				default:
					// Unknown extended ban type - treat as regular ban for now
					if adding {
						channel.AddBan(mask)
						appliedModes = append(appliedModes, "+b")
					} else {
						channel.RemoveBan(mask)
						appliedModes = append(appliedModes, "-b")
					}
					appliedArgs = append(appliedArgs, mask)
				}
			} else {
				// Regular ban
				if adding {
					channel.AddBan(mask)
					appliedModes = append(appliedModes, "+b")
				} else {
					channel.RemoveBan(mask)
					appliedModes = append(appliedModes, "-b")
				}
				appliedArgs = append(appliedArgs, mask)
			}

		case 'R': // registered users only
			channel.SetMode('R', adding)
			if adding {
				appliedModes = append(appliedModes, "+R")
			} else {
				appliedModes = append(appliedModes, "-R")
			}

		case 'M': // muted (only ops can speak)
			channel.SetMode('M', adding)
			if adding {
				appliedModes = append(appliedModes, "+M")
			} else {
				appliedModes = append(appliedModes, "-M")
			}

		case 'N': // no notice messages
			channel.SetMode('N', adding)
			if adding {
				appliedModes = append(appliedModes, "+N")
			} else {
				appliedModes = append(appliedModes, "-N")
			}

		case 'C': // no CTCP messages
			channel.SetMode('C', adding)
			if adding {
				appliedModes = append(appliedModes, "+C")
			} else {
				appliedModes = append(appliedModes, "-C")
			}

		case 'c': // no colors/formatting
			channel.SetMode('c', adding)
			if adding {
				appliedModes = append(appliedModes, "+c")
			} else {
				appliedModes = append(appliedModes, "-c")
			}

		case 'S': // SSL/TLS users only
			channel.SetMode('S', adding)
			if adding {
				appliedModes = append(appliedModes, "+S")
			} else {
				appliedModes = append(appliedModes, "-S")
			}

		case 'O': // opers only
			channel.SetMode('O', adding)
			if adding {
				appliedModes = append(appliedModes, "+O")
			} else {
				appliedModes = append(appliedModes, "-O")
			}

		case 'z': // reduced moderation (halfops can use voice)
			channel.SetMode('z', adding)
			if adding {
				appliedModes = append(appliedModes, "+z")
			} else {
				appliedModes = append(appliedModes, "-z")
			}

		case 'D': // delay join (users don't appear until they speak)
			channel.SetMode('D', adding)
			if adding {
				appliedModes = append(appliedModes, "+D")
			} else {
				appliedModes = append(appliedModes, "-D")
			}

		case 'G': // word filter/profanity filter
			channel.SetMode('G', adding)
			if adding {
				appliedModes = append(appliedModes, "+G")
			} else {
				appliedModes = append(appliedModes, "-G")
			}

		case 'f': // flood protection
			if adding {
				if argIndex >= len(args) {
					continue
				}
				floodSettings := args[argIndex]
				argIndex++
				// Parse flood settings (e.g., "10:5" = 10 messages in 5 seconds)
				channel.SetFloodSettings(floodSettings)
				channel.SetMode('f', true)
				appliedModes = append(appliedModes, "+f")
				appliedArgs = append(appliedArgs, floodSettings)
			} else {
				channel.SetFloodSettings("")
				channel.SetMode('f', false)
				appliedModes = append(appliedModes, "-f")
			}

		case 'j': // join throttling
			if adding {
				if argIndex >= len(args) {
					continue
				}
				joinThrottle := args[argIndex]
				argIndex++
				// Parse join throttle (e.g., "3:10" = 3 joins per 10 seconds)
				channel.SetJoinThrottle(joinThrottle)
				channel.SetMode('j', true)
				appliedModes = append(appliedModes, "+j")
				appliedArgs = append(appliedArgs, joinThrottle)
			} else {
				channel.SetJoinThrottle("")
				channel.SetMode('j', false)
				appliedModes = append(appliedModes, "-j")
			}

		default:
			// Unknown mode - ignore for now
		}
	}

	// Broadcast mode changes to all channel members
	if len(appliedModes) > 0 {
		modeChangeMsg := fmt.Sprintf("MODE %s %s", target, strings.Join(appliedModes, ""))
		if len(appliedArgs) > 0 {
			modeChangeMsg += " " + strings.Join(appliedArgs, " ")
		}

		for _, client := range channel.GetClients() {
			client.SendFrom(c.Prefix(), modeChangeMsg)
		}
	}
}

// handleTopic handles TOPIC command
func (c *Client) handleTopic(parts []string) {
	if len(parts) < 2 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "TOPIC :Not enough parameters")
		return
	}

	channelName := parts[1]
	if !isChannelName(channelName) {
		c.SendNumeric(ERR_NOSUCHCHANNEL, channelName+" :No such channel")
		return
	}

	channel := c.server.GetChannel(channelName)
	if channel == nil {
		c.SendNumeric(ERR_NOSUCHCHANNEL, channelName+" :No such channel")
		return
	}

	if !c.IsInChannel(channelName) {
		c.SendNumeric(ERR_NOTONCHANNEL, channelName+" :You're not on that channel")
		return
	}

	// If no topic provided, return current topic
	if len(parts) == 2 {
		topic := channel.Topic()
		if topic == "" {
			c.SendNumeric(RPL_NOTOPIC, channelName+" :No topic is set")
		} else {
			c.SendNumeric(RPL_TOPIC, fmt.Sprintf("%s :%s", channelName, topic))
		}
		return
	}

	// Check if user can set topic (for now, anyone in channel can)
	// TODO: Add proper +t mode checking
	newTopic := strings.Join(parts[2:], " ")
	if len(newTopic) > 0 && newTopic[0] == ':' {
		newTopic = newTopic[1:]
	}

	channel.SetTopic(newTopic, c.Nick())

	// Broadcast topic change to all channel members
	for _, client := range channel.GetClients() {
		client.SendFrom(c.Prefix(), fmt.Sprintf("TOPIC %s :%s", channelName, newTopic))
	}
}

// handleAway handles AWAY command
func (c *Client) handleAway(parts []string) {
	if len(parts) == 1 {
		// Remove away status
		c.SetAway("")
		c.SendNumeric(RPL_UNAWAY, ":You are no longer marked as being away")
		return
	}

	// Set away message
	awayMsg := strings.Join(parts[1:], " ")
	if len(awayMsg) > 0 && awayMsg[0] == ':' {
		awayMsg = awayMsg[1:]
	}

	c.SetAway(awayMsg)
	c.SendNumeric(RPL_NOWAWAY, ":You have been marked as being away")
}

// handleList handles LIST command
func (c *Client) handleList() {
	c.SendNumeric(RPL_LISTSTART, "Channel :Users  Name")

	for _, channel := range c.server.GetChannels() {
		// For now, show all channels (TODO: Add proper mode checking for secret channels)
		userCount := len(channel.GetClients())
		topic := channel.Topic()
		if topic == "" {
			topic = ""
		}
		c.SendNumeric(RPL_LIST, fmt.Sprintf("%s %d :%s", channel.Name(), userCount, topic))
	}

	c.SendNumeric(RPL_LISTEND, ":End of /LIST")
}

// handleInvite handles INVITE command
func (c *Client) handleInvite(parts []string) {
	if len(parts) < 3 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "INVITE :Not enough parameters")
		return
	}

	nick := parts[1]
	channelName := parts[2]

	target := c.server.GetClient(nick)
	if target == nil {
		c.SendNumeric(ERR_NOSUCHNICK, nick+" :No such nick/channel")
		return
	}

	if !isChannelName(channelName) {
		c.SendNumeric(ERR_NOSUCHCHANNEL, channelName+" :No such channel")
		return
	}

	channel := c.server.GetChannel(channelName)
	if channel == nil {
		c.SendNumeric(ERR_NOSUCHCHANNEL, channelName+" :No such channel")
		return
	}

	if !c.IsInChannel(channelName) {
		c.SendNumeric(ERR_NOTONCHANNEL, channelName+" :You're not on that channel")
		return
	}

	if target.IsInChannel(channelName) {
		c.SendNumeric(ERR_USERONCHANNEL, fmt.Sprintf("%s %s :is already on channel", nick, channelName))
		return
	}

	// TODO: Check if user has operator privileges for invite-only channels

	// Send invite to target
	target.SendFrom(c.Prefix(), fmt.Sprintf("INVITE %s %s", target.Nick(), channelName))
	c.SendNumeric(RPL_INVITING, fmt.Sprintf("%s %s", target.Nick(), channelName))
}

// handleKick handles KICK command
func (c *Client) handleKick(parts []string) {
	if len(parts) < 3 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "KICK :Not enough parameters")
		return
	}

	channelName := parts[1]
	nick := parts[2]
	reason := "No reason given"
	if len(parts) > 3 {
		reason = strings.Join(parts[3:], " ")
		if len(reason) > 0 && reason[0] == ':' {
			reason = reason[1:]
		}
	}

	if !isChannelName(channelName) {
		c.SendNumeric(ERR_NOSUCHCHANNEL, channelName+" :No such channel")
		return
	}

	channel := c.server.GetChannel(channelName)
	if channel == nil {
		c.SendNumeric(ERR_NOSUCHCHANNEL, channelName+" :No such channel")
		return
	}

	if !c.IsInChannel(channelName) {
		c.SendNumeric(ERR_NOTONCHANNEL, channelName+" :You're not on that channel")
		return
	}

	target := c.server.GetClient(nick)
	if target == nil {
		c.SendNumeric(ERR_NOSUCHNICK, nick+" :No such nick/channel")
		return
	}

	if !target.IsInChannel(channelName) {
		c.SendNumeric(ERR_USERNOTINCHANNEL, fmt.Sprintf("%s %s :They aren't on that channel", nick, channelName))
		return
	}

	// God Mode users cannot be kicked
	if target.HasGodMode() {
		c.SendNumeric(ERR_NOPRIVILEGES, fmt.Sprintf("%s :Cannot kick user (GOD MODE)", nick))
		c.sendSnomask('o', fmt.Sprintf("GOD MODE: %s attempted to kick %s from %s but was blocked", c.Nick(), target.Nick(), channelName))
		return
	}

	// TODO: Check if user has operator privileges
	// For now, allow anyone to kick (will fix with proper channel modes)

	// Broadcast kick to all channel members
	kickMsg := fmt.Sprintf("KICK %s %s :%s", channelName, target.Nick(), reason)
	for _, client := range channel.GetClients() {
		client.SendFrom(c.Prefix(), kickMsg)
	}

	// Remove target from channel
	channel.RemoveClient(target)
	target.RemoveChannel(channelName)
}

// handleKill handles KILL command (operator only)
func (c *Client) handleKill(parts []string) {
	if !c.IsOper() {
		c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied- You're not an IRC operator")
		return
	}

	if len(parts) < 2 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "KILL :Not enough parameters")
		return
	}

	nick := parts[1]
	reason := "Killed by operator"
	if len(parts) > 2 {
		reason = strings.Join(parts[2:], " ")
		if len(reason) > 0 && reason[0] == ':' {
			reason = reason[1:]
		}
	}

	target := c.server.GetClient(nick)
	if target == nil {
		c.SendNumeric(ERR_NOSUCHNICK, nick+" :No such nick/channel")
		return
	}

	// Can't kill other operators
	if target.IsOper() {
		c.SendNumeric(ERR_CANTKILLSERVER, ":You can't kill other operators")
		return
	}

	// Send kill message to target and disconnect
	target.SendMessage(fmt.Sprintf("ERROR :Killed (%s (%s))", c.Nick(), reason))

	// Broadcast to other operators
	for _, client := range c.server.GetClients() {
		if client.IsOper() && client != c {
			client.SendMessage(fmt.Sprintf(":%s WALLOPS :%s killed %s (%s)",
				c.server.config.Server.Name, c.Nick(), target.Nick(), reason))
		}
	}

	// Disconnect the target
	target.conn.Close()
}

// handleOper handles OPER command
func (c *Client) handleOper(parts []string) {
	if len(parts) < 3 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "OPER :Not enough parameters")
		return
	}

	if c.server == nil || c.server.config == nil {
		c.SendNumeric(ERR_NOOPERHOST, ":No O-lines for your host")
		return
	}

	name := parts[1]
	password := parts[2]

	// Check if opers are enabled
	if !c.server.config.Features.EnableOper {
		c.SendNumeric(ERR_NOOPERHOST, ":O-lines are disabled")
		return
	}

	var matchedOper *Oper
	var operConfig *OperConfig

	// Try to load advanced oper configuration first
	if c.server.config.OperConfig.Enable {
		var err error
		operConfig, err = LoadOperConfig(c.server.config.OperConfig.ConfigFile)
		if err == nil {
			oper := operConfig.GetOper(name)
			if oper != nil && oper.Password == password {
				// TODO: Implement proper host matching
				if oper.Host == "*@localhost" || oper.Host == "*@*" {
					matchedOper = oper
				}
			}
		}
	}

	// Fallback to legacy configuration
	if matchedOper == nil {
		for _, oper := range c.server.config.Opers {
			if oper.Name == name && oper.Password == password {
				// Check host mask (simplified - just check if it matches *@localhost for now)
				if oper.Host == "*@localhost" || oper.Host == "*@*" {
					// Convert legacy oper to new format for consistency
					matchedOper = &Oper{
						Name:     oper.Name,
						Password: oper.Password,
						Host:     oper.Host,
						Class:    oper.Class,
						Flags:    oper.Flags,
					}
					break
				}
			}
		}
	}

	if matchedOper == nil {
		c.SendNumeric(ERR_PASSWDMISMATCH, ":Password incorrect")
		return
	}

	// Set operator status
	c.SetOper(true)
	c.SetOperClass(matchedOper.Class)

	// Set operator user mode
	c.SetMode('o', true)
	c.SetMode('s', true) // Enable server notices by default
	c.SetMode('w', true) // Enable wallops by default

	// Set default snomasks for new operators
	c.SetSnomask('c', true) // Client connects/disconnects
	c.SetSnomask('o', true) // Oper-up messages
	c.SetSnomask('s', true) // Server messages

	// Get operator class information for display
	var className string
	if operConfig != nil {
		class := operConfig.GetOperClass(matchedOper.Class)
		if class != nil {
			className = fmt.Sprintf(" (%s)", class.Description)
		}
	}

	c.SendNumeric(RPL_YOUREOPER, ":You are now an IRC operator"+className)
	c.SendNumeric(RPL_SNOMASK, fmt.Sprintf("%s :Server notice mask", c.GetSnomasks()))

	// Send mode change notification
	c.SendMessage(fmt.Sprintf(":%s MODE %s :+osw", c.Nick(), c.Nick()))

	// Send snomask to other operators
	operSymbol := c.GetOperSymbol()
	c.sendSnomask('o', fmt.Sprintf("%s%s (%s@%s) is now an IRC operator%s",
		operSymbol, c.Nick(), c.User(), c.Host(), className))
}

// handleSnomask handles SNOMASK command (server notice masks for operators)
func (c *Client) handleSnomask(parts []string) {
	if !c.IsOper() {
		c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied- You're not an IRC operator")
		return
	}

	if len(parts) < 2 {
		// Show current snomasks
		current := c.GetSnomasks()
		if current == "" {
			current = "+"
		}
		c.SendNumeric(RPL_SNOMASK, fmt.Sprintf("%s :Server notice mask", current))
		return
	}

	modeString := parts[1]
	adding := true
	changed := false

	for _, char := range modeString {
		switch char {
		case '+':
			adding = true
		case '-':
			adding = false
		case 'c': // Client connects/disconnects
			c.SetSnomask('c', adding)
			changed = true
		case 'k': // Kill messages
			c.SetSnomask('k', adding)
			changed = true
		case 'o': // Oper-up messages
			c.SetSnomask('o', adding)
			changed = true
		case 'x': // X-line (ban) messages
			c.SetSnomask('x', adding)
			changed = true
		case 'f': // Flood messages
			c.SetSnomask('f', adding)
			changed = true
		case 'n': // Nick changes
			c.SetSnomask('n', adding)
			changed = true
		case 's': // Server messages
			c.SetSnomask('s', adding)
			changed = true
		case 'd': // Debug messages (TechIRCd special)
			c.SetSnomask('d', adding)
			changed = true
		}
	}

	if changed {
		current := c.GetSnomasks()
		if current == "" {
			current = "+"
		}
		c.SendNumeric(RPL_SNOMASK, fmt.Sprintf("%s :Server notice mask", current))
	}
}

// handleGlobalNotice handles GLOBALNOTICE command (TechIRCd special oper command)
func (c *Client) handleGlobalNotice(parts []string) {
	if !c.IsOper() {
		c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied- You're not an IRC operator")
		return
	}

	if len(parts) < 2 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "GLOBALNOTICE :Not enough parameters")
		return
	}

	message := strings.Join(parts[1:], " ")
	if len(message) > 0 && message[0] == ':' {
		message = message[1:]
	}

	// Send global notice to all users
	for _, client := range c.server.GetClients() {
		client.SendMessage(fmt.Sprintf(":%s NOTICE %s :[GLOBAL] %s",
			c.server.config.Server.Name, client.Nick(), message))
	}

	// Send snomask to operators watching global notices
	c.sendSnomask('s', fmt.Sprintf("Global notice from %s: %s", c.Nick(), message))
}

// handleWallops handles WALLOPS command (send to users with +w mode)
func (c *Client) handleWallops(parts []string) {
	if !c.IsOper() {
		c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied- You're not an IRC operator")
		return
	}

	if len(parts) < 2 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "WALLOPS :Not enough parameters")
		return
	}

	message := strings.Join(parts[1:], " ")
	if len(message) > 0 && message[0] == ':' {
		message = message[1:]
	}

	// Send to all users with +w mode
	for _, client := range c.server.GetClients() {
		if client.HasMode('w') {
			client.SendMessage(fmt.Sprintf(":%s WALLOPS :%s", c.Nick(), message))
		}
	}
}

// handleOperWall handles OPERWALL command (message to all operators)
func (c *Client) handleOperWall(parts []string) {
	if !c.IsOper() {
		c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied- You're not an IRC operator")
		return
	}

	if len(parts) < 2 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "OPERWALL :Not enough parameters")
		return
	}

	message := strings.Join(parts[1:], " ")
	if len(message) > 0 && message[0] == ':' {
		message = message[1:]
	}

	// Send to all operators
	for _, client := range c.server.GetClients() {
		if client.IsOper() {
			client.SendMessage(fmt.Sprintf(":%s WALLOPS :%s", c.Nick(), message))
		}
	}
}

// handleRehash handles REHASH command (reload configuration)
func (c *Client) handleRehash() {
	if !c.IsOper() {
		c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied- You're not an IRC operator")
		return
	}

	// Reload configuration
	if c.server != nil {
		err := c.server.ReloadConfig()
		if err != nil {
			c.SendMessage(fmt.Sprintf(":%s NOTICE %s :*** REHASH failed: %s",
				c.server.config.Server.Name, c.Nick(), err.Error()))
			c.sendSnomask('s', fmt.Sprintf("REHASH failed by %s: %s", c.Nick(), err.Error()))
		} else {
			c.SendMessage(fmt.Sprintf(":%s NOTICE %s :*** Configuration reloaded successfully",
				c.server.config.Server.Name, c.Nick()))
			c.sendSnomask('s', fmt.Sprintf("Configuration reloaded by %s", c.Nick()))
		}
	}
}

// handleTrace handles TRACE command (show server connection tree)
func (c *Client) handleTrace(_ []string) {
	if !c.IsOper() {
		c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied- You're not an IRC operator")
		return
	}

	// Show basic server info (simplified implementation)
	c.SendMessage(fmt.Sprintf(":%s 200 %s Link %s %s %s",
		c.server.config.Server.Name, c.Nick(),
		c.server.config.Server.Version,
		c.server.config.Server.Name,
		"TechIRCd"))

	clientCount := len(c.server.GetClients())
	c.SendMessage(fmt.Sprintf(":%s 262 %s %s :End of TRACE with %d clients",
		c.server.config.Server.Name, c.Nick(),
		c.server.config.Server.Name, clientCount))
}

// isValidNickname checks if a nickname is valid
func isValidNickname(nick string) bool {
	if len(nick) == 0 || len(nick) > 30 {
		return false
	}

	// First character must be a letter or special char
	first := nick[0]
	if !((first >= 'A' && first <= 'Z') || (first >= 'a' && first <= 'z') ||
		first == '[' || first == ']' || first == '\\' || first == '`' ||
		first == '_' || first == '^' || first == '{' || first == '|' || first == '}') {
		return false
	}

	// Rest can be letters, digits, or special chars
	for i := 1; i < len(nick); i++ {
		c := nick[i]
		if !((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
			c == '[' || c == ']' || c == '\\' || c == '`' ||
			c == '_' || c == '^' || c == '{' || c == '|' || c == '}' || c == '-') {
			return false
		}
	}

	return true
}

// isValidChannelName checks if a channel name is valid
func isValidChannelName(name string) bool {
	if len(name) == 0 || len(name) > 50 {
		return false
	}

	return name[0] == '#' || name[0] == '&' || name[0] == '!' || name[0] == '+'
}

// isChannelName checks if a name is a channel name
func isChannelName(name string) bool {
	if len(name) == 0 {
		return false
	}
	return name[0] == '#' || name[0] == '&' || name[0] == '!' || name[0] == '+'
}

// Server linking commands

// handleConnect handles CONNECT command for server linking
func (c *Client) handleConnect(parts []string) {
	if !c.IsOper() {
		c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied- You're not an IRC operator")
		return
	}

	if len(parts) < 3 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "CONNECT :Not enough parameters")
		return
	}

	serverName := parts[1]
	portStr := parts[2]
	host := "localhost"
	if len(parts) > 3 {
		host = parts[3]
	}

	port := 0
	if _, err := fmt.Sscanf(portStr, "%d", &port); err != nil || port <= 0 || port > 65535 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "CONNECT :Invalid port number")
		return
	}

	// Check if server is already connected
	if c.server.GetLinkedServer(serverName) != nil {
		c.SendMessage(fmt.Sprintf(":%s NOTICE %s :*** Server %s is already connected",
			c.server.config.Server.Name, c.Nick(), serverName))
		return
	}

	// Find the server in configuration
	var linkConfig *struct {
		Name        string `json:"name"`
		Host        string `json:"host"`
		Port        int    `json:"port"`
		Password    string `json:"password"`
		AutoConnect bool   `json:"auto_connect"`
		Hub         bool   `json:"hub"`
		Description string `json:"description"`
	}

	for _, link := range c.server.config.Linking.Links {
		if link.Name == serverName {
			linkConfig = &link
			break
		}
	}

	if linkConfig == nil {
		c.SendMessage(fmt.Sprintf(":%s NOTICE %s :*** No link configuration found for %s",
			c.server.config.Server.Name, c.Nick(), serverName))
		return
	}

	// Use configured values, but allow override from command
	connectHost := linkConfig.Host
	connectPort := linkConfig.Port
	if host != "localhost" {
		connectHost = host
	}
	if portStr != "0" {
		connectPort = port
	}

	c.SendMessage(fmt.Sprintf(":%s NOTICE %s :*** Attempting to connect to %s at %s:%d",
		c.server.config.Server.Name, c.Nick(), serverName, connectHost, connectPort))

	// Send snomask to other operators
	c.sendSnomask('s', fmt.Sprintf("%s initiated connection to %s (%s:%d)",
		c.Nick(), serverName, connectHost, connectPort))

	// Start connection attempt
	go c.server.connectToServer(linkConfig.Name, connectHost, connectPort,
		linkConfig.Password, linkConfig.Hub, linkConfig.Description)
}

// handleSquit handles SQUIT command for server disconnection
func (c *Client) handleSquit(parts []string) {
	if !c.IsOper() {
		c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied- You're not an IRC operator")
		return
	}

	if len(parts) < 2 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "SQUIT :Not enough parameters")
		return
	}

	serverName := parts[1]
	reason := "Operator requested disconnection"
	if len(parts) > 2 {
		reason = strings.Join(parts[2:], " ")
		if len(reason) > 0 && reason[0] == ':' {
			reason = reason[1:]
		}
	}

	linkedServer := c.server.GetLinkedServer(serverName)
	if linkedServer == nil {
		c.SendMessage(fmt.Sprintf(":%s NOTICE %s :*** Server %s is not connected",
			c.server.config.Server.Name, c.Nick(), serverName))
		return
	}

	c.SendMessage(fmt.Sprintf(":%s NOTICE %s :*** Disconnecting from %s: %s",
		c.server.config.Server.Name, c.Nick(), serverName, reason))

	// Send snomask to other operators
	c.sendSnomask('s', fmt.Sprintf("%s disconnected %s: %s", c.Nick(), serverName, reason))

	// Send SQUIT to remote server
	linkedServer.SendMessage(fmt.Sprintf("SQUIT %s :%s", c.server.config.Server.Name, reason))

	// Remove the server
	c.server.RemoveLinkedServer(serverName)
}

// handleLinks handles LINKS command to show server links
func (c *Client) handleLinks() {
	if !c.IsRegistered() {
		c.SendNumeric(ERR_NOTREGISTERED, ":You have not registered")
		return
	}

	// Show our server first
	c.SendNumeric(364, fmt.Sprintf("%s %s :0 %s",
		c.server.config.Server.Name, c.server.config.Server.Name, c.server.config.Server.Description))

	// Show linked servers
	linkedServers := c.server.GetLinkedServers()
	for _, linkedServer := range linkedServers {
		if linkedServer.IsConnected() {
			c.SendNumeric(364, fmt.Sprintf("%s %s :1 %s",
				linkedServer.Name(), c.server.config.Server.Name, linkedServer.Description()))
		}
	}

	c.SendNumeric(365, ":End of /LINKS list")
}

// handleUserhost handles USERHOST command
func (c *Client) handleUserhost(parts []string) {
	if !c.IsRegistered() {
		c.SendNumeric(ERR_NOTREGISTERED, ":You have not registered")
		return
	}

	if len(parts) < 2 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "USERHOST :Not enough parameters")
		return
	}

	var responses []string
	// USERHOST can take up to 5 nicknames
	maxNicks := 5
	if len(parts)-1 < maxNicks {
		maxNicks = len(parts) - 1
	}

	for i := 1; i <= maxNicks && i < len(parts); i++ {
		nick := parts[i]
		target := c.server.GetClient(nick)
		if target != nil {
			response := target.Nick() + "="
			if target.IsOper() {
				response += "*"
			}
			if target.Away() != "" {
				response += "-"
			} else {
				response += "+"
			}
			response += target.User() + "@" + target.HostForUser(c)
			responses = append(responses, response)
		}
	}

	if len(responses) > 0 {
		c.SendNumeric(RPL_USERHOST, ":"+strings.Join(responses, " "))
	}
}

// handleIson handles ISON command
func (c *Client) handleIson(parts []string) {
	if !c.IsRegistered() {
		c.SendNumeric(ERR_NOTREGISTERED, ":You have not registered")
		return
	}

	if len(parts) < 2 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "ISON :Not enough parameters")
		return
	}

	var onlineNicks []string
	for i := 1; i < len(parts); i++ {
		nick := parts[i]
		if c.server.GetClient(nick) != nil {
			onlineNicks = append(onlineNicks, nick)
		}
	}

	c.SendNumeric(RPL_ISON, ":"+strings.Join(onlineNicks, " "))
}

// handleTime handles TIME command
func (c *Client) handleTime() {
	if !c.IsRegistered() {
		c.SendNumeric(ERR_NOTREGISTERED, ":You have not registered")
		return
	}

	currentTime := time.Now().Format("Mon Jan 2 15:04:05 2006")
	c.SendNumeric(RPL_TIME, fmt.Sprintf("%s :%s", c.server.config.Server.Name, currentTime))
}

// handleVersion handles VERSION command
func (c *Client) handleVersion() {
	if !c.IsRegistered() {
		c.SendNumeric(ERR_NOTREGISTERED, ":You have not registered")
		return
	}

	version := fmt.Sprintf("TechIRCd-%s", c.server.config.Server.Version)
	c.SendNumeric(RPL_VERSION, fmt.Sprintf("%s %s :Go IRC Server by ComputerTech312",
		version, c.server.config.Server.Name))
}

// handleAdmin handles ADMIN command
func (c *Client) handleAdmin() {
	if !c.IsRegistered() {
		c.SendNumeric(ERR_NOTREGISTERED, ":You have not registered")
		return
	}

	c.SendNumeric(RPL_ADMINME, fmt.Sprintf("%s :Administrative info", c.server.config.Server.Name))
	c.SendNumeric(RPL_ADMINLOC1, ":TechIRCd Server")
	c.SendNumeric(RPL_ADMINLOC2, ":Modern IRC Server written in Go")
	c.SendNumeric(RPL_ADMINEMAIL, fmt.Sprintf(":%s", c.server.config.Server.AdminInfo))
}

// handleInfo handles INFO command
func (c *Client) handleInfo() {
	if !c.IsRegistered() {
		c.SendNumeric(ERR_NOTREGISTERED, ":You have not registered")
		return
	}

	infoLines := []string{
		"TechIRCd - Modern IRC Server",
		"",
		"Version: " + c.server.config.Server.Version,
		"Network: " + c.server.config.Server.Network,
		"Description: " + c.server.config.Server.Description,
		"",
		"Features:",
		"- Full RFC 2812 compliance",
		"- Advanced operator system with hierarchical classes",
		"- Comprehensive channel management",
		"- God Mode and Stealth Mode",
		"- Revolutionary WHOIS system",
		"- Server linking support",
		"- Real-time health monitoring",
		"",
		"Written in Go by ComputerTech312",
		"https://github.com/ComputerTech312/TechIRCd",
	}

	for _, line := range infoLines {
		c.SendNumeric(RPL_INFO, ":"+line)
	}
	c.SendNumeric(RPL_ENDOFINFO, ":End of /INFO list")
}

// handleLusers handles LUSERS command
func (c *Client) handleLusers() {
	if !c.IsRegistered() {
		c.SendNumeric(ERR_NOTREGISTERED, ":You have not registered")
		return
	}

	totalUsers := len(c.server.GetClients())
	totalChannels := len(c.server.GetChannels())

	// Count operators
	operCount := 0
	invisibleCount := 0
	for _, client := range c.server.GetClients() {
		if client.IsOper() {
			operCount++
		}
		if client.HasMode('i') {
			invisibleCount++
		}
	}

	visibleUsers := totalUsers - invisibleCount
	unknownConnections := 0 // Connections that haven't completed registration

	c.SendNumeric(RPL_LUSERCLIENT, fmt.Sprintf(":There are %d users and %d invisible on 1 servers",
		visibleUsers, invisibleCount))

	if operCount > 0 {
		c.SendNumeric(RPL_LUSEROP, fmt.Sprintf("%d :operator(s) online", operCount))
	}

	if unknownConnections > 0 {
		c.SendNumeric(RPL_LUSERUNKNOWN, fmt.Sprintf("%d :unknown connection(s)", unknownConnections))
	}

	if totalChannels > 0 {
		c.SendNumeric(RPL_LUSERCHANNELS, fmt.Sprintf("%d :channels formed", totalChannels))
	}

	c.SendNumeric(RPL_LUSERME, fmt.Sprintf(":I have %d clients and 1 servers", totalUsers))
}

// handleStats handles STATS command
func (c *Client) handleStats(parts []string) {
	if !c.IsRegistered() {
		c.SendNumeric(ERR_NOTREGISTERED, ":You have not registered")
		return
	}

	// Only operators can use most STATS queries
	if !c.IsOper() {
		c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied- You're not an IRC operator")
		return
	}

	statsType := "l" // default to links
	if len(parts) > 1 {
		statsType = strings.ToLower(parts[1])
	}

	switch statsType {
	case "l": // Links
		// Show server links
		linkedServers := c.server.GetLinkedServers()
		for name, server := range linkedServers {
			if server.IsConnected() {
				c.SendNumeric(RPL_STATSLINKINFO, fmt.Sprintf("%s 0 0 0 0 0 0", name))
			}
		}

	case "u": // Uptime
		uptime := time.Since(c.server.healthMonitor.startTime)
		c.SendNumeric(RPL_STATSUPTIME, fmt.Sprintf(":Server Up %d days %d:%02d:%02d",
			int(uptime.Hours())/24, int(uptime.Hours())%24,
			int(uptime.Minutes())%60, int(uptime.Seconds())%60))

	case "o": // Operators
		for _, oper := range c.server.config.Opers {
			c.SendNumeric(RPL_STATSOLINE, fmt.Sprintf("O %s * %s", oper.Host, oper.Name))
		}

	case "m": // Commands
		// Would show command usage statistics
		c.SendNumeric(RPL_STATSCOMMANDS, "PRIVMSG 1234 567 890")
		c.SendNumeric(RPL_STATSCOMMANDS, "JOIN 456 123 789")
		c.SendNumeric(RPL_STATSCOMMANDS, "PART 234 89 456")

	default:
		c.SendNumeric(ERR_NOSUCHSERVER, fmt.Sprintf("%s :No such server", statsType))
		return
	}

	c.SendNumeric(RPL_ENDOFSTATS, fmt.Sprintf("%s :End of /STATS report", statsType))
}

// handleSilence handles SILENCE command (user-level blocking)
func (c *Client) handleSilence(parts []string) {
	if !c.IsRegistered() {
		c.SendNumeric(ERR_NOTREGISTERED, ":You have not registered")
		return
	}

	if len(parts) < 2 {
		// List current silence masks
		if len(c.silenceList) == 0 {
			c.SendNumeric(RPL_ENDOFSILELIST, ":End of Silence list")
			return
		}

		for _, mask := range c.silenceList {
			c.SendNumeric(RPL_SILELIST, mask)
		}
		c.SendNumeric(RPL_ENDOFSILELIST, ":End of Silence list")
		return
	}

	mask := parts[1]
	if strings.HasPrefix(mask, "-") {
		// Remove from silence list
		mask = mask[1:]
		for i, silenceMask := range c.silenceList {
			if silenceMask == mask {
				c.silenceList = append(c.silenceList[:i], c.silenceList[i+1:]...)
				c.SendMessage(fmt.Sprintf(":%s NOTICE %s :Removed %s from silence list",
					c.server.config.Server.Name, c.Nick(), mask))
				return
			}
		}
	} else {
		// Add to silence list
		if len(c.silenceList) >= 32 { // Limit silence list size
			c.SendNumeric(ERR_SILELISTFULL, ":Your silence list is full")
			return
		}

		c.silenceList = append(c.silenceList, mask)
		c.SendMessage(fmt.Sprintf(":%s NOTICE %s :Added %s to silence list",
			c.server.config.Server.Name, c.Nick(), mask))
	}
}

// handleMonitor handles MONITOR command (IRCv3)
func (c *Client) handleMonitor(parts []string) {
	if !c.IsRegistered() {
		c.SendNumeric(ERR_NOTREGISTERED, ":You have not registered")
		return
	}

	if len(parts) < 2 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "MONITOR :Not enough parameters")
		return
	}

	subcommand := strings.ToUpper(parts[1])

	switch subcommand {
	case "+": // Add nicknames to monitor list
		if len(parts) < 3 {
			return
		}

		nicks := strings.Split(parts[2], ",")
		var online []string
		var offline []string

		for _, nick := range nicks {
			if len(c.monitorList) >= 100 { // Limit monitor list size
				c.SendNumeric(ERR_MONLISTFULL, fmt.Sprintf("%d %s :Monitor list is full",
					100, nick))
				break
			}

			c.monitorList = append(c.monitorList, nick)

			if target := c.server.GetClient(nick); target != nil {
				online = append(online, nick)
			} else {
				offline = append(offline, nick)
			}
		}

		if len(online) > 0 {
			c.SendNumeric(RPL_MONONLINE, ":"+strings.Join(online, ","))
		}
		if len(offline) > 0 {
			c.SendNumeric(RPL_MONOFFLINE, ":"+strings.Join(offline, ","))
		}

	case "-": // Remove nicknames from monitor list
		if len(parts) < 3 {
			return
		}

		nicks := strings.Split(parts[2], ",")
		for _, nick := range nicks {
			for i, monitorNick := range c.monitorList {
				if strings.EqualFold(monitorNick, nick) {
					c.monitorList = append(c.monitorList[:i], c.monitorList[i+1:]...)
					break
				}
			}
		}

	case "C": // Clear monitor list
		c.monitorList = []string{}

	case "L": // List monitor list
		if len(c.monitorList) == 0 {
			c.SendNumeric(RPL_ENDOFMONLIST, ":End of MONITOR list")
			return
		}

		// Send in batches of 10
		for i := 0; i < len(c.monitorList); i += 10 {
			end := i + 10
			if end > len(c.monitorList) {
				end = len(c.monitorList)
			}
			batch := c.monitorList[i:end]
			c.SendNumeric(RPL_MONLIST, ":"+strings.Join(batch, ","))
		}
		c.SendNumeric(RPL_ENDOFMONLIST, ":End of MONITOR list")

	case "S": // Show status
		var online []string
		var offline []string

		for _, nick := range c.monitorList {
			if c.server.GetClient(nick) != nil {
				online = append(online, nick)
			} else {
				offline = append(offline, nick)
			}
		}

		if len(online) > 0 {
			c.SendNumeric(RPL_MONONLINE, ":"+strings.Join(online, ","))
		}
		if len(offline) > 0 {
			c.SendNumeric(RPL_MONOFFLINE, ":"+strings.Join(offline, ","))
		}
	}
}

// handleAuthenticate handles AUTHENTICATE command for SASL
func (c *Client) handleAuthenticate(parts []string) {
	if len(parts) < 2 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "AUTHENTICATE :Not enough parameters")
		return
	}

	if c.IsRegistered() {
		c.SendNumeric(ERR_ALREADYREGISTRED, ":You may not reregister")
		return
	}

	mechanism := strings.ToUpper(parts[1])

	// Support PLAIN mechanism for now
	if mechanism == "PLAIN" {
		c.saslMech = "PLAIN"
		c.SendMessage("AUTHENTICATE +")
		return
	}

	if mechanism == "*" {
		// Abort SASL authentication
		c.saslMech = ""
		c.saslData = ""
		c.SendNumeric(ERR_SASLABORTED, ":SASL authentication aborted")
		return
	}

	if c.saslMech == "PLAIN" && mechanism != "PLAIN" {
		// This is the SASL data
		saslData := parts[1]

		if saslData == "+" {
			// Empty response, wait for data
			return
		}

		// Decode base64 SASL data (simplified - in real implementation would use proper base64)
		// Format: authzid\0authcid\0password
		// For simplicity, we'll expect username:password format
		if strings.Contains(saslData, ":") {
			credentials := strings.SplitN(saslData, ":", 2)
			if len(credentials) == 2 {
				username := credentials[0]
				password := credentials[1]

				// Check against configured accounts (simplified)
				if c.authenticateUser(username, password) {
					c.account = username
					c.SendNumeric(RPL_SASLSUCCESS, ":SASL authentication successful")
				} else {
					c.SendNumeric(ERR_SASLFAIL, ":SASL authentication failed")
				}
			} else {
				c.SendNumeric(ERR_SASLFAIL, ":SASL authentication failed")
			}
		} else {
			c.SendNumeric(ERR_SASLFAIL, ":SASL authentication failed")
		}

		c.saslMech = ""
		c.saslData = ""
		return
	}

	// Unsupported mechanism
	c.SendNumeric(ERR_SASLNOTSUPP, fmt.Sprintf("%s :are available SASL mechanisms", "PLAIN"))
}

// authenticateUser checks user credentials (simplified implementation)
func (c *Client) authenticateUser(username, password string) bool {
	// In a real implementation, this would check against a database or services
	// For now, we'll use a simple hardcoded check
	accounts := map[string]string{
		"admin":    "password123",
		"testuser": "test123",
	}

	if storedPassword, exists := accounts[username]; exists {
		return storedPassword == password
	}

	return false
}

// handleHelpop handles HELPOP command - help for IRC operators and users
func (c *Client) handleHelpop(parts []string) {
	if !c.IsRegistered() {
		c.SendNumeric(ERR_NOTREGISTERED, ":You have not registered")
		return
	}

	topic := "index"
	if len(parts) > 1 {
		topic = strings.ToLower(parts[1])
	}

	switch topic {
	case "index", "help", "":
		c.sendHelpIndex()

	case "modes":
		c.sendHelpModes()

	case "commands":
		c.sendHelpCommands()

	case "chanmodes":
		c.sendHelpChannelModes()

	case "usermodes":
		c.sendHelpUserModes()

	case "oper":
		c.sendHelpOper()

	case "god", "godmode":
		c.sendHelpGodMode()

	case "stealth", "stealthmode":
		c.sendHelpStealthMode()

	case "ircv3":
		c.sendHelpIRCv3()

	case "linking":
		c.sendHelpLinking()

	case "examples":
		c.sendHelpExamples()

	default:
		c.SendMessage(fmt.Sprintf(":%s 292 %s :*** Unknown HELPOP topic: %s",
			c.server.config.Server.Name, c.Nick(), topic))
		c.SendMessage(fmt.Sprintf(":%s 292 %s :*** Use /HELPOP INDEX for available topics",
			c.server.config.Server.Name, c.Nick()))
	}
}

// sendHelpIndex sends the main help index
func (c *Client) sendHelpIndex() {
	c.SendMessage(fmt.Sprintf(":%s 292 %s :*** TechIRCd Help System ***",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Available help topics:",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  COMMANDS    - List of available commands",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  CHANMODES   - Channel modes (+mntispkl etc)",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  USERMODES   - User modes (+iwsoBGS etc)",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  OPER        - IRC Operator commands",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  GODMODE     - God Mode features (+G)",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  STEALTHMODE - Stealth Mode features (+S)",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  IRCV3       - IRCv3 capabilities",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  LINKING     - Server linking",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  EXAMPLES    - Usage examples",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Usage: /HELPOP <topic>",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 294 %s :End of /HELPOP",
		c.server.config.Server.Name, c.Nick()))
}

// sendHelpChannelModes sends help about channel modes
func (c *Client) sendHelpChannelModes() {
	c.SendMessage(fmt.Sprintf(":%s 292 %s :*** TechIRCd Channel Modes ***",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Basic Modes:",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +m  Moderated (only voiced users can speak)",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +n  No external messages",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +t  Topic protection (ops only)",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +i  Invite only",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +s  Secret channel",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +p  Private channel",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +k  Channel key/password",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +l  User limit",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Advanced Modes:",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +R  Registered users only",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +M  Muted (only ops can speak)",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +N  No notice messages",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +C  No CTCP messages",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +c  No colors/formatting",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +S  SSL/TLS users only",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +O  Opers only",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +z  Reduced moderation",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +D  Delay join",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +G  Word filter",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +f  Flood protection",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +j  Join throttling",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :User Levels:",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +q  Owner/Founder (~)",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +o  Operator (@)",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +h  Half-operator (%%)",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +v  Voice (+)",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 294 %s :End of /HELPOP",
		c.server.config.Server.Name, c.Nick()))
}

// sendHelpUserModes sends help about user modes
func (c *Client) sendHelpUserModes() {
	c.SendMessage(fmt.Sprintf(":%s 292 %s :*** TechIRCd User Modes ***",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Basic Modes:",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +i  Invisible (hidden from WHO)",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +w  Wallops (receive WALLOPS messages)",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +s  Server notices (oper only)",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +o  IRC Operator (automatic)",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +r  Registered (services only)",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +x  Host masking",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +z  SSL/TLS connection (automatic)",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +B  Bot flag",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :TechIRCd Special Modes:",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +G  God Mode (oper only, ultimate power)",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  +S  Stealth Mode (oper only, invisible to users)",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Usage: /MODE yournick +mode",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 294 %s :End of /HELPOP",
		c.server.config.Server.Name, c.Nick()))
}

// sendHelpGodMode sends help about God Mode
func (c *Client) sendHelpGodMode() {
	c.SendMessage(fmt.Sprintf(":%s 292 %s :*** TechIRCd God Mode (+G) ***",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :God Mode gives ultimate channel override powers:",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :• Bypass ALL channel restrictions (+k, +l, +i, +b)",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :• Set channel modes without operator privileges",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :• Cannot be kicked from channels",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :• Immune to channel bans",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :• Join invite-only channels instantly",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Requirements:",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :• Must be an IRC operator (/OPER)",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :• Operator class needs 'god_mode' permission",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Usage: /MODE yournick +G",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 294 %s :End of /HELPOP",
		c.server.config.Server.Name, c.Nick()))
}

// sendHelpCommands sends help about available commands
func (c *Client) sendHelpCommands() {
	c.SendMessage(fmt.Sprintf(":%s 292 %s :*** TechIRCd Commands ***",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Basic Commands:",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /JOIN #channel     - Join a channel",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /PART #channel     - Leave a channel",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /PRIVMSG target    - Send a message",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /NOTICE target     - Send a notice",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /WHOIS nick        - Get user info",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /MODE target       - Set modes",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /TOPIC #channel    - Set/view topic",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /KICK #chan nick   - Kick user",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /INVITE nick #chan - Invite user",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Operator Commands:",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /OPER name pass    - Become operator",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /KILL nick reason  - Disconnect user",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /WALLOPS message   - Message to +w users",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /REHASH            - Reload config",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :IRCv3 Commands:",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /CAP LS            - List capabilities",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /MONITOR +nick     - Monitor user",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /AUTHENTICATE      - SASL authentication",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 294 %s :End of /HELPOP",
		c.server.config.Server.Name, c.Nick()))
}

// sendHelpModes sends general help about modes
func (c *Client) sendHelpModes() {
	c.SendMessage(fmt.Sprintf(":%s 292 %s :*** TechIRCd Modes ***",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :TechIRCd supports both user modes and channel modes.",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Use /HELPOP USERMODES for user mode help",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Use /HELPOP CHANMODES for channel mode help",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 294 %s :End of /HELPOP",
		c.server.config.Server.Name, c.Nick()))
}

// sendHelpOper sends help about operator commands
func (c *Client) sendHelpOper() {
	if !c.IsOper() {
		c.SendMessage(fmt.Sprintf(":%s 292 %s :*** You are not an IRC operator ***",
			c.server.config.Server.Name, c.Nick()))
		c.SendMessage(fmt.Sprintf(":%s 294 %s :End of /HELPOP",
			c.server.config.Server.Name, c.Nick()))
		return
	}

	c.SendMessage(fmt.Sprintf(":%s 292 %s :*** TechIRCd Operator Help ***",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Available operator commands:",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /KILL nick reason      - Disconnect user",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /WALLOPS message       - Send to +w users",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /OPERWALL message      - Send to operators",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /GLOBALNOTICE message  - Send to all users",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /REHASH                - Reload configuration",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /SNOMASK +modes        - Set notice masks",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /CONNECT server port   - Link to server",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /SQUIT server reason   - Unlink server",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Your operator class: %s",
		c.server.config.Server.Name, c.Nick(), c.OperClass()))
	c.SendMessage(fmt.Sprintf(":%s 294 %s :End of /HELPOP",
		c.server.config.Server.Name, c.Nick()))
}

// sendHelpStealthMode sends help about Stealth Mode
func (c *Client) sendHelpStealthMode() {
	c.SendMessage(fmt.Sprintf(":%s 292 %s :*** TechIRCd Stealth Mode (+S) ***",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Stealth Mode makes you invisible to regular users:",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :• Hidden from WHO commands",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :• Hidden from NAMES lists",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :• WHOIS restricted (operators can still see you)",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :• Still visible to other operators",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Requirements:",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :• Must be an IRC operator (/OPER)",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :• Operator class needs 'stealth_mode' permission",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Usage: /MODE yournick +S",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 294 %s :End of /HELPOP",
		c.server.config.Server.Name, c.Nick()))
}

// sendHelpIRCv3 sends help about IRCv3 features
func (c *Client) sendHelpIRCv3() {
	c.SendMessage(fmt.Sprintf(":%s 292 %s :*** TechIRCd IRCv3 Support ***",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :TechIRCd supports modern IRCv3 features:",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :• server-time        - Message timestamps",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :• account-notify     - Account change notifications",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :• away-notify        - Away status notifications",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :• extended-join      - Enhanced JOIN with account info",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :• multi-prefix       - Multiple user prefixes",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :• sasl               - SASL authentication",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :• message-tags       - Message tag support",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :• echo-message       - Message echo back to sender",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Usage: /CAP REQ :server-time account-notify",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 294 %s :End of /HELPOP",
		c.server.config.Server.Name, c.Nick()))
}

// sendHelpLinking sends help about server linking
func (c *Client) sendHelpLinking() {
	if !c.IsOper() {
		c.SendMessage(fmt.Sprintf(":%s 292 %s :*** Operator access required ***",
			c.server.config.Server.Name, c.Nick()))
		c.SendMessage(fmt.Sprintf(":%s 294 %s :End of /HELPOP",
			c.server.config.Server.Name, c.Nick()))
		return
	}

	c.SendMessage(fmt.Sprintf(":%s 292 %s :*** TechIRCd Server Linking ***",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Server linking commands:",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /CONNECT server port   - Connect to remote server",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /SQUIT server reason   - Disconnect server",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /LINKS                 - Show linked servers",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Servers must be configured in linking.json",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 294 %s :End of /HELPOP",
		c.server.config.Server.Name, c.Nick()))
}

// sendHelpExamples sends usage examples
func (c *Client) sendHelpExamples() {
	c.SendMessage(fmt.Sprintf(":%s 292 %s :*** TechIRCd Usage Examples ***",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Join a channel:",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /JOIN #help",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Set channel to moderated:",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /MODE #help +m",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Enable God Mode (operators only):",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /MODE yournick +G",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :Request IRCv3 capabilities:",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 292 %s :  /CAP REQ :server-time message-tags",
		c.server.config.Server.Name, c.Nick()))
	c.SendMessage(fmt.Sprintf(":%s 294 %s :End of /HELPOP",
		c.server.config.Server.Name, c.Nick()))
}

// expandBanMask expands partial ban masks to full IRC hostmask format
func expandBanMask(mask string) string {
	// If it's already a full hostmask (contains ! and @), return as-is
	if strings.Contains(mask, "!") && strings.Contains(mask, "@") {
		return mask
	}

	// If it's just a nickname, expand to nick!*@*
	if !strings.Contains(mask, "!") && !strings.Contains(mask, "@") {
		return fmt.Sprintf("%s!*@*", mask)
	}

	// If it has ! but no @, assume it's nick!user format, add @*
	if strings.Contains(mask, "!") && !strings.Contains(mask, "@") {
		return fmt.Sprintf("%s@*", mask)
	}

	// If it has @ but no !, assume it's @host format, add *!*
	if !strings.Contains(mask, "!") && strings.Contains(mask, "@") {
		return fmt.Sprintf("*!*%s", mask)
	}

	// Default case - return as-is
	return mask
}

// Services/Admin Commands

// handleChghost handles CHGHOST command - change user's hostname
func (c *Client) handleChghost(parts []string) {
	if !c.IsOper() {
		c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied- You're not an IRC operator")
		return
	}

	if len(parts) < 3 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "CHGHOST :Not enough parameters")
		return
	}

	targetNick := parts[1]
	newHost := parts[2]

	target := c.server.GetClient(targetNick)
	if target == nil {
		c.SendNumeric(ERR_NOSUCHNICK, targetNick+" :No such nick/channel")
		return
	}

	oldHost := target.Host()
	target.host = newHost

	// Notify the target user
	target.SendMessage(fmt.Sprintf(":%s NOTICE %s :*** Your hostname has been changed to %s", 
		c.server.config.Server.Name, target.Nick(), newHost))

	// Notify all channels the user is in
	for channelName := range target.channels {
		channel := c.server.GetChannel(channelName)
		if channel != nil {
			channel.BroadcastFrom(c.server.config.Server.Name, 
				fmt.Sprintf("CHGHOST %s %s %s", target.Nick(), oldHost, newHost), nil)
		}
	}

	// Send snomask to operators
	c.server.sendSnomask('o', fmt.Sprintf("%s used CHGHOST to change %s's hostname from %s to %s", 
		c.Nick(), target.Nick(), oldHost, newHost))

	c.SendMessage(fmt.Sprintf(":%s NOTICE %s :*** Changed hostname of %s from %s to %s", 
		c.server.config.Server.Name, c.Nick(), target.Nick(), oldHost, newHost))
}

// handleSvsnick handles SVSNICK command - services force nickname change
func (c *Client) handleSvsnick(parts []string) {
	if !c.IsOper() {
		c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied- You're not an IRC operator")
		return
	}

	if len(parts) < 3 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "SVSNICK :Not enough parameters")
		return
	}

	targetNick := parts[1]
	newNick := parts[2]

	target := c.server.GetClient(targetNick)
	if target == nil {
		c.SendNumeric(ERR_NOSUCHNICK, targetNick+" :No such nick/channel")
		return
	}

	// Check if new nick is already in use
	if c.server.IsNickInUse(newNick) {
		c.SendNumeric(ERR_NICKNAMEINUSE, newNick+" :Nickname is already in use")
		return
	}

	oldNick := target.Nick()

	// Send NICK change to all channels the user is in
	for channelName := range target.channels {
		channel := c.server.GetChannel(channelName)
		if channel != nil {
			channel.BroadcastFrom(target.Prefix(), fmt.Sprintf("NICK :%s", newNick), nil)
		}
	}

	// Change the nickname
	target.SetNick(newNick)

	// Notify the user
	target.SendMessage(fmt.Sprintf(":%s NICK :%s", oldNick, newNick))

	// Send snomask to operators
	c.server.sendSnomask('o', fmt.Sprintf("%s used SVSNICK to change %s's nickname to %s", 
		c.Nick(), oldNick, newNick))

	c.SendMessage(fmt.Sprintf(":%s NOTICE %s :*** Changed nickname of %s to %s", 
		c.server.config.Server.Name, c.Nick(), oldNick, newNick))
}

// handleSvsmode handles SVSMODE command - services force mode change
func (c *Client) handleSvsmode(parts []string) {
	if !c.IsOper() {
		c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied- You're not an IRC operator")
		return
	}

	if len(parts) < 3 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "SVSMODE :Not enough parameters")
		return
	}

	target := parts[1]
	modeString := parts[2]

	// Check if target is a channel or user
	if strings.HasPrefix(target, "#") || strings.HasPrefix(target, "&") {
		// Channel mode change
		channel := c.server.GetChannel(target)
		if channel == nil {
			c.SendNumeric(ERR_NOSUCHCHANNEL, target+" :No such channel")
			return
		}

		// Apply mode changes (simplified implementation)
		channel.BroadcastFrom(c.server.config.Server.Name, fmt.Sprintf("MODE %s %s", target, modeString), nil)
		
		// Send snomask to operators
		c.server.sendSnomask('o', fmt.Sprintf("%s used SVSMODE to set %s on %s", 
			c.Nick(), modeString, target))
	} else {
		// User mode change
		targetClient := c.server.GetClient(target)
		if targetClient == nil {
			c.SendNumeric(ERR_NOSUCHNICK, target+" :No such nick/channel")
			return
		}

		// Apply user mode changes
		targetClient.SendMessage(fmt.Sprintf(":%s MODE %s %s", 
			c.server.config.Server.Name, target, modeString))

		// Send snomask to operators
		c.server.sendSnomask('o', fmt.Sprintf("%s used SVSMODE to set %s on %s", 
			c.Nick(), modeString, target))
	}

	c.SendMessage(fmt.Sprintf(":%s NOTICE %s :*** Set mode %s on %s", 
		c.server.config.Server.Name, c.Nick(), modeString, target))
}

// handleSamode handles SAMODE command - services admin mode change
func (c *Client) handleSamode(parts []string) {
	if !c.IsOper() {
		c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied- You're not an IRC operator")
		return
	}

	if len(parts) < 3 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "SAMODE :Not enough parameters")
		return
	}

	target := parts[1]
	modeString := parts[2]
	args := parts[3:]

	if !strings.HasPrefix(target, "#") && !strings.HasPrefix(target, "&") {
		c.SendNumeric(ERR_NOSUCHCHANNEL, target+" :No such channel")
		return
	}

	channel := c.server.GetChannel(target)
	if channel == nil {
		c.SendNumeric(ERR_NOSUCHCHANNEL, target+" :No such channel")
		return
	}

	// Build complete mode command
	modeCommand := fmt.Sprintf("MODE %s %s", target, modeString)
	if len(args) > 0 {
		modeCommand += " " + strings.Join(args, " ")
	}

	// Broadcast mode change
	channel.BroadcastFrom(c.Prefix(), modeCommand, nil)

	// Send snomask to operators
	c.server.sendSnomask('o', fmt.Sprintf("%s used SAMODE: %s", c.Nick(), modeCommand))
}

// handleSanick handles SANICK command - services admin nickname change
func (c *Client) handleSanick(parts []string) {
	if !c.IsOper() {
		c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied- You're not an IRC operator")
		return
	}

	if len(parts) < 3 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "SANICK :Not enough parameters")
		return
	}

	targetNick := parts[1]
	newNick := parts[2]

	target := c.server.GetClient(targetNick)
	if target == nil {
		c.SendNumeric(ERR_NOSUCHNICK, targetNick+" :No such nick/channel")
		return
	}

	// Check if new nick is already in use
	if c.server.IsNickInUse(newNick) {
		c.SendNumeric(ERR_NICKNAMEINUSE, newNick+" :Nickname is already in use")
		return
	}

	oldNick := target.Nick()

	// Send NICK change to all channels the user is in
	for channelName := range target.channels {
		channel := c.server.GetChannel(channelName)
		if channel != nil {
			channel.BroadcastFrom(target.Prefix(), fmt.Sprintf("NICK :%s", newNick), nil)
		}
	}

	// Change the nickname
	target.SetNick(newNick)

	// Notify the user
	target.SendMessage(fmt.Sprintf(":%s NICK :%s", oldNick, newNick))
	target.SendMessage(fmt.Sprintf(":%s NOTICE %s :*** Your nickname has been changed by services", 
		c.server.config.Server.Name, newNick))

	// Send snomask to operators
	c.server.sendSnomask('o', fmt.Sprintf("%s used SANICK to change %s's nickname to %s", 
		c.Nick(), oldNick, newNick))

	c.SendMessage(fmt.Sprintf(":%s NOTICE %s :*** Changed nickname of %s to %s", 
		c.server.config.Server.Name, c.Nick(), oldNick, newNick))
}

// handleSakick handles SAKICK command - services admin kick
func (c *Client) handleSakick(parts []string) {
	if !c.IsOper() {
		c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied- You're not an IRC operator")
		return
	}

	if len(parts) < 3 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "SAKICK :Not enough parameters")
		return
	}

	channelName := parts[1]
	targetNick := parts[2]
	reason := "Services Admin Kick"
	
	if len(parts) > 3 {
		reason = strings.Join(parts[3:], " ")
		if strings.HasPrefix(reason, ":") {
			reason = reason[1:]
		}
	}

	channel := c.server.GetChannel(channelName)
	if channel == nil {
		c.SendNumeric(ERR_NOSUCHCHANNEL, channelName+" :No such channel")
		return
	}

	target := c.server.GetClient(targetNick)
	if target == nil {
		c.SendNumeric(ERR_NOSUCHNICK, targetNick+" :No such nick/channel")
		return
	}

	if !target.IsInChannel(channelName) {
		c.SendNumeric(ERR_USERNOTINCHANNEL, targetNick+" "+channelName+" :They aren't on that channel")
		return
	}

	// Broadcast kick message
	kickMsg := fmt.Sprintf("KICK %s %s :%s", channelName, targetNick, reason)
	channel.BroadcastFrom(c.Prefix(), kickMsg, nil)

	// Remove user from channel
	channel.RemoveClient(target)
	target.RemoveChannel(channelName)

	// Send snomask to operators
	c.server.sendSnomask('o', fmt.Sprintf("%s used SAKICK to kick %s from %s (%s)", 
		c.Nick(), targetNick, channelName, reason))
}

// handleSapart handles SAPART command - services admin part
func (c *Client) handleSapart(parts []string) {
	if !c.IsOper() {
		c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied- You're not an IRC operator")
		return
	}

	if len(parts) < 3 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "SAPART :Not enough parameters")
		return
	}

	targetNick := parts[1]
	channelName := parts[2]
	reason := "Services Admin Part"
	
	if len(parts) > 3 {
		reason = strings.Join(parts[3:], " ")
		if strings.HasPrefix(reason, ":") {
			reason = reason[1:]
		}
	}

	target := c.server.GetClient(targetNick)
	if target == nil {
		c.SendNumeric(ERR_NOSUCHNICK, targetNick+" :No such nick/channel")
		return
	}

	channel := c.server.GetChannel(channelName)
	if channel == nil {
		c.SendNumeric(ERR_NOSUCHCHANNEL, channelName+" :No such channel")
		return
	}

	if !target.IsInChannel(channelName) {
		c.SendNumeric(ERR_NOTONCHANNEL, channelName+" :You're not on that channel")
		return
	}

	// Broadcast part message
	partMsg := fmt.Sprintf("PART %s :%s", channelName, reason)
	channel.BroadcastFrom(target.Prefix(), partMsg, nil)

	// Remove user from channel
	channel.RemoveClient(target)
	target.RemoveChannel(channelName)

	// Send snomask to operators
	c.server.sendSnomask('o', fmt.Sprintf("%s used SAPART to part %s from %s (%s)", 
		c.Nick(), targetNick, channelName, reason))

	c.SendMessage(fmt.Sprintf(":%s NOTICE %s :*** Forced %s to part %s", 
		c.server.config.Server.Name, c.Nick(), targetNick, channelName))
}

// handleSajoin handles SAJOIN command - services admin join
func (c *Client) handleSajoin(parts []string) {
	if !c.IsOper() {
		c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied- You're not an IRC operator")
		return
	}

	if len(parts) < 3 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "SAJOIN :Not enough parameters")
		return
	}

	targetNick := parts[1]
	channelName := parts[2]

	target := c.server.GetClient(targetNick)
	if target == nil {
		c.SendNumeric(ERR_NOSUCHNICK, targetNick+" :No such nick/channel")
		return
	}

	if target.IsInChannel(channelName) {
		c.SendMessage(fmt.Sprintf(":%s NOTICE %s :*** %s is already on %s", 
			c.server.config.Server.Name, c.Nick(), targetNick, channelName))
		return
	}

	// Get or create channel
	channel := c.server.GetOrCreateChannel(channelName)

	// Add user to channel
	channel.AddClient(target)
	target.AddChannel(channel)

	// Broadcast join message
	joinMsg := fmt.Sprintf("JOIN :%s", channelName)
	channel.BroadcastFrom(target.Prefix(), joinMsg, nil)

	// Send topic if exists
	if channel.Topic() != "" {
		target.SendNumeric(RPL_TOPIC, channelName+" :"+channel.Topic())
		target.SendNumeric(RPL_TOPICWHOTIME, fmt.Sprintf("%s %s %d", 
			channelName, channel.TopicBy(), channel.TopicTime().Unix()))
	}

	// Send names list
	target.sendNames(channel)

	// Send snomask to operators
	c.server.sendSnomask('o', fmt.Sprintf("%s used SAJOIN to join %s to %s", 
		c.Nick(), targetNick, channelName))

	c.SendMessage(fmt.Sprintf(":%s NOTICE %s :*** Forced %s to join %s", 
		c.server.config.Server.Name, c.Nick(), targetNick, channelName))
}

// handleWhowas handles WHOWAS command
func (c *Client) handleWhowas(parts []string) {
	if len(parts) < 2 {
		c.SendNumeric(ERR_NONICKNAMEGIVEN, ":No nickname given")
		return
	}

	nick := parts[1]
	
	// Send end of whowas (we don't store history)
	c.SendNumeric(RPL_ENDOFWHOWAS, nick+" :End of WHOWAS")
}

// handleMotd handles MOTD command
func (c *Client) handleMotd() {
	motd := []string{
		"Welcome to TechIRCd!",
		"",
		"This is a modern IRC server with IRCv3 features.",
		"For help, join #help or contact an operator.",
		"",
		"Enjoy your stay!",
	}

	c.SendNumeric(RPL_MOTDSTART, ":- " + c.server.config.Server.Name + " Message of the day - ")
	for _, line := range motd {
		c.SendNumeric(RPL_MOTD, ":- " + line)
	}
	c.SendNumeric(RPL_ENDOFMOTD, ":End of MOTD command")
}

// handleRules handles RULES command
func (c *Client) handleRules() {
	rules := []string{
		"Server Rules:",
		"",
		"1. Be respectful to other users",
		"2. No spamming or flooding",
		"3. No harassment or abuse",
		"4. Keep channels on-topic",
		"5. Follow operator instructions",
		"",
		"Violations may result in kicks, bans, or K-lines.",
	}

	c.SendNumeric(RPL_RULESSTART, ":- " + c.server.config.Server.Name + " server rules:")
	for _, line := range rules {
		c.SendNumeric(RPL_RULES, ":- " + line)
	}
	c.SendNumeric(RPL_ENDOFRULES, ":End of RULES command")
}

// handleMap handles MAP command
func (c *Client) handleMap() {
	c.SendNumeric(RPL_MAP, fmt.Sprintf(":%s (Users: %d)", 
		c.server.config.Server.Name, len(c.server.clients)))
	c.SendNumeric(RPL_MAPEND, ":End of MAP")
}

// handleKnock handles KNOCK command
func (c *Client) handleKnock(parts []string) {
	if len(parts) < 2 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "KNOCK :Not enough parameters")
		return
	}

	channelName := parts[1]
	message := "has asked for an invite"
	if len(parts) > 2 {
		message = strings.Join(parts[2:], " ")
	}

	c.server.mu.RLock()
	channel, exists := c.server.channels[channelName]
	c.server.mu.RUnlock()

	if !exists {
		c.SendNumeric(ERR_NOSUCHCHANNEL, channelName+" :No such channel")
		return
	}

	// Check if user is already on channel
	if channel.HasClient(c) {
		c.SendNumeric(ERR_KNOCKONCHAN, channelName+" :You are already on that channel")
		return
	}

	// Check if channel is invite-only
	if !channel.HasMode('i') {
		c.SendNumeric(ERR_CHANOPEN, channelName+" :Channel is open")
		return
	}

	// Send knock to channel operators
	knockMsg := fmt.Sprintf("KNOCK %s :%s (%s@%s) %s", 
		channelName, c.Nick(), c.User(), c.Host(), message)
	
	// Broadcast to operators only
	clients := channel.GetClients()
	for _, client := range clients {
		if channel.IsOperator(client) || channel.IsOwner(client) || channel.IsAdmin(client) {
			client.SendMessage(":" + c.server.config.Server.Name + " " + knockMsg)
		}
	}
	
	c.SendNumeric(RPL_KNOCKDLVR, channelName+" :Your KNOCK has been delivered")
}

// handleSetname handles SETNAME command (change real name)
func (c *Client) handleSetname(parts []string) {
	if len(parts) < 2 {
		c.SendNumeric(ERR_NEEDMOREPARAMS, "SETNAME :Not enough parameters")
		return
	}

	newRealname := strings.Join(parts[1:], " ")
	if strings.HasPrefix(newRealname, ":") {
		newRealname = newRealname[1:]
	}

	if len(newRealname) > 50 {
		c.SendNumeric(ERR_INVALIDUSERNAME, ":Real name too long")
		return
	}

	c.SetRealname(newRealname)

	// Broadcast setname to users who can see this client
	setnameMsg := fmt.Sprintf("SETNAME :%s", newRealname)
	
	// Send to all channels user is in
	c.server.mu.RLock()
	for _, channel := range c.server.channels {
		if channel.HasClient(c) {
			channel.BroadcastFrom(c.Prefix(), setnameMsg, nil)
		}
	}
	c.server.mu.RUnlock()

	c.SendMessage(fmt.Sprintf(":%s NOTICE %s :*** Your real name is now '%s'", 
		c.server.config.Server.Name, c.Nick(), newRealname))
}

// handleDie handles DIE command (shutdown server)
func (c *Client) handleDie() {
	if !c.IsOper() {
		c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied- You're not an IRC operator")
		return
	}

	// Check for die permission
	if !c.HasOperPermission("die") {
		c.SendNumeric(ERR_NOPRIVILEGES, ":Permission Denied- You need die permission")
		return
	}

	// Send snomask to operators
	c.server.sendSnomask('o', fmt.Sprintf("%s (%s@%s) issued DIE command", 
		c.Nick(), c.User(), c.Host()))

	// Send notice to all users
	dieMsg := fmt.Sprintf(":%s NOTICE * :*** Server shutting down by %s", 
		c.server.config.Server.Name, c.Nick())
	
	c.server.mu.RLock()
	for _, client := range c.server.clients {
		client.SendMessage(dieMsg)
	}
	c.server.mu.RUnlock()

	// Shutdown server
	go c.server.Shutdown()
}
