package main

import (
	"fmt"
	"regexp"
	"sort"
	"strings"
	"time"

	"go-ts/pkg/audio"
	"go-ts/pkg/ts3client"

	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
)

// Focus indicates which panel has focus
type Focus int

const (
	FocusChannels Focus = iota
	FocusChat
	FocusInput
)

// ChatMessage represents a message in the chat
type ChatMessage struct {
	Time    time.Time
	Sender  string
	Content string
}

// ChannelNode represents a channel in the tree
type ChannelNode struct {
	ID       uint64
	Name     string
	Users    []UserNode
	Expanded bool
	Selected bool
	Depth    int
}

// UserNode represents a user in a channel
type UserNode struct {
	ID       uint16
	Nickname string
	Talking  bool
	Muted    bool
	IsMe     bool
}

// Model is the Bubble Tea model for our TUI
type Model struct {
	// Connection
	serverAddr string
	nickname   string
	client     *ts3client.Client
	connected  bool
	connecting bool
	serverName string
	selfID     uint16
	ping       int

	// UI State
	focus         Focus
	width, height int
	channels      []ChannelNode
	selectedIdx   int
	chatMessages  []ChatMessage
	logMessages   []string // Debug logs shown in chat panel
	inputText     string
	inputActive   bool

	// Error handling
	lastError string

	// Voice activity
	talkingClients map[uint16]bool // ClientID -> isTalking

	// Audio state
	audioPlayer   *audio.Player
	audioCapturer *audio.Capturer
	playbackVol   int  // 0-100
	micLevel      int  // 0-100 (current input level)
	isMuted       bool // Mic muted
	isPTT         bool // Push-to-talk active

	// Program reference for sending messages from event handlers
	program *tea.Program
	showLog bool
}

// addLog adds a message to the log panel
func (m *Model) addLog(format string, args ...any) {
	msg := fmt.Sprintf(format, args...)
	m.logMessages = append(m.logMessages, msg)
	// Keep last 50 messages
	if len(m.logMessages) > 50 {
		m.logMessages = m.logMessages[1:]
	}
}

// NewModel creates a new TUI model
func NewModel(serverAddr, nickname string) *Model {
	return &Model{
		serverAddr:     serverAddr,
		nickname:       nickname,
		focus:          FocusChannels,
		channels:       []ChannelNode{},
		chatMessages:   []ChatMessage{},
		logMessages:    []string{"Starting..."},
		talkingClients: make(map[uint16]bool),
		playbackVol:    80, // Default 80% volume
	}
}

// SetProgram sets the tea.Program reference for sending messages from event handlers
func (m *Model) SetProgram(p *tea.Program) {
	m.program = p
}

// Init is called when the program starts
func (m *Model) Init() tea.Cmd {
	return tea.Batch(
		m.connectToServer(),
		tea.SetWindowTitle("TeamSpeak TUI"),
	)
}

// TS3Event wraps events from the TS3 client
type TS3Event struct {
	Type string
	Data map[string]any
}

// connectToServer initiates connection to TeamSpeak
func (m *Model) connectToServer() tea.Cmd {
	return func() tea.Msg {
		m.connecting = true

		client := ts3client.New(m.serverAddr, ts3client.Config{
			Nickname: m.nickname,
		})

		return ts3ClientMsg{client: client}
	}
}

type ts3ClientMsg struct {
	client *ts3client.Client
}

type connectedMsg struct {
	clientID   uint16
	serverName string
}

type channelListMsg struct {
	channels []*ts3client.Channel
}

type clientEnterMsg struct {
	clientID  uint16
	nickname  string
	channelID uint64
}

type clientLeftMsg struct {
	clientID uint16
}

type chatMsg struct {
	senderID   uint16
	senderName string
	message    string
}

type talkingStatusMsg struct {
	clientID uint16
	talking  bool
}

type errorMsg struct {
	err string
}

type micLevelMsg int // Mic level 0-100

type tickMsg time.Time

// Update handles messages and user input
func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.WindowSizeMsg:
		m.width = msg.Width
		m.height = msg.Height
		return m, nil

	case tea.KeyMsg:
		return m.handleKeyPress(msg)

	case ts3ClientMsg:
		m.client = msg.client
		m.connecting = true

		// Register event handlers that need to send messages to the TUI
		if m.program != nil {
			m.client.On(ts3client.EventTalkingStatus, func(e *ts3client.TalkingStatusEvent) {
				m.program.Send(talkingStatusMsg{
					clientID: e.ClientID,
					talking:  e.Talking,
				})
			})

			m.client.On(ts3client.EventMessage, func(e *ts3client.MessageEvent) {
				m.program.Send(chatMsg{
					senderID:   e.SenderID,
					senderName: e.SenderName,
					message:    e.Message,
				})
			})

			// Handle incoming audio - play through speakers
			m.client.On(ts3client.EventAudio, func(e *ts3client.AudioEvent) {
				if m.audioPlayer != nil {
					m.audioPlayer.PlayPCM(e.SenderID, e.PCM)
				}
			})
		}

		// Initialize audio player
		player, err := audio.NewPlayer()
		if err != nil {
			m.addLog("Audio player init failed: %v", err)
		} else {
			m.audioPlayer = player
			m.audioPlayer.SetVolume(float32(m.playbackVol) / 100.0)
			m.audioPlayer.Start()
			m.addLog("Audio player initialized")
		}

		// Initialize audio capturer
		capturer, err := audio.NewCapturer()
		if err != nil {
			m.addLog("Audio capturer init failed: %v", err)
		} else {
			m.audioCapturer = capturer
			// Set callback to send audio to server when PTT is active
			m.audioCapturer.SetCallback(func(samples []int16) {
				if m.isPTT && m.client != nil && !m.isMuted {
					m.client.SendAudio(samples)
				}
				// Update mic level for display
				if m.program != nil {
					m.program.Send(micLevelMsg(m.audioCapturer.GetLevel()))
				}
			})
			m.addLog("Audio capturer initialized")
		}

		// Connect asynchronously
		m.client.ConnectAsync()

		// Set up a ticker to poll for state changes
		return m, tea.Tick(500*time.Millisecond, func(t time.Time) tea.Msg {
			return tickMsg(t)
		})

	case tickMsg:
		// Poll client state
		if m.client != nil {
			// Check if connected
			if !m.connected {
				info := m.client.GetSelfInfo()
				serverInfo := m.client.GetServerInfo()
				m.addLog("Tick: selfInfo=%v, serverInfo=%v", info != nil, serverInfo != nil)
				if info != nil && serverInfo != nil {
					m.connected = true
					m.selfID = info.ClientID
					m.serverName = serverInfo.Name
					m.addLog("Connected! ClientID=%d, Server=%s", m.selfID, m.serverName)

					// Get channels
					channels := m.client.GetChannels()
					m.addLog("Got %d channels", len(channels))
					m.updateChannelList(channels)
				}
			} else {
				// Update channel list periodically
				channels := m.client.GetChannels()
				if len(channels) != len(m.channels) {
					m.addLog("Channel update: got %d channels (had %d)", len(channels), len(m.channels))
				}
				m.updateChannelList(channels)
			}
		}

		// Update mic level when PTT is active (multiply for better visibility)
		if m.isPTT && m.audioCapturer != nil {
			level := m.audioCapturer.GetLevel() * 4 // Boost for visibility
			if level > 100 {
				level = 100
			}
			m.micLevel = level
		} else {
			m.micLevel = 0 // Reset when not transmitting
		}

		// Continue ticking (100ms for responsive mic level)
		return m, tea.Tick(100*time.Millisecond, func(t time.Time) tea.Msg {
			return tickMsg(t)
		})

	case connectedMsg:
		m.connected = true
		m.connecting = false
		m.selfID = msg.clientID
		m.serverName = msg.serverName
		return m, nil

	case channelListMsg:
		m.updateChannelList(msg.channels)
		return m, nil

	case errorMsg:
		m.lastError = msg.err
		return m, nil

	case talkingStatusMsg:
		// Update talking status for client
		if msg.talking {
			m.talkingClients[msg.clientID] = true
		} else {
			delete(m.talkingClients, msg.clientID)
		}
		return m, nil

	case chatMsg:
		m.chatMessages = append(m.chatMessages, ChatMessage{
			Time:    time.Now(),
			Sender:  msg.senderName,
			Content: msg.message,
		})
		// Keep last 100 messages
		if len(m.chatMessages) > 100 {
			m.chatMessages = m.chatMessages[1:]
		}
		return m, nil

	case micLevelMsg:
		// Update microphone level for display
		m.micLevel = int(msg)
		return m, nil

	case logMsg:
		// External log message
		m.addLog("%s", string(msg))
		return m, nil
	}

	return m, nil
}

type logMsg string

func (m *Model) updateChannelList(channels []*ts3client.Channel) {
	// Build adjacency map: ParentID -> PreviousID(Order) -> Channel
	levelMap := make(map[uint64]map[uint64]*ts3client.Channel)
	for _, ch := range channels {
		if levelMap[ch.ParentID] == nil {
			levelMap[ch.ParentID] = make(map[uint64]*ts3client.Channel)
		}
		levelMap[ch.ParentID][ch.Order] = ch
	}

	var sortedNodes []ChannelNode

	// Recursive function to flatten the tree in order
	var visit func(parentID uint64, depth int)
	visit = func(parentID uint64, depth int) {
		prevID := uint64(0)
		for {
			ch, ok := levelMap[parentID][prevID]
			if !ok {
				break // End of list for this parent
			}

			// Create node
			node := ChannelNode{
				ID:       ch.ID,
				Name:     ch.Name,
				Users:    []UserNode{},
				Expanded: true,
				Depth:    depth,
			}

			// Get users in this channel
			for _, cl := range m.client.GetClients() {
				if cl.ChannelID == ch.ID {
					node.Users = append(node.Users, UserNode{
						ID:       cl.ID,
						Nickname: cl.Nickname,
						IsMe:     cl.ID == m.selfID,
						Talking:  m.talkingClients[cl.ID],
					})
				}
			}

			// Sort users by ID for stable ordering
			sort.Slice(node.Users, func(i, j int) bool {
				return node.Users[i].ID < node.Users[j].ID
			})

			sortedNodes = append(sortedNodes, node)

			// Recursively visit children
			visit(ch.ID, depth+1)

			// Move to next sibling
			prevID = ch.ID
		}
	}

	// Start from root (ParentID = 0)
	visit(0, 0)

	// In case there are orphaned channels (shouldn't happen on standard server), standard logic ignores them.
	// We'll stick to valid tree.

	m.channels = sortedNodes
}

func (m *Model) handleKeyPress(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
	// 1. Absolute Globals (Always active)
	switch msg.String() {
	case "ctrl+c":
		if m.client != nil {
			m.client.Disconnect()
		}
		// Cleanup audio
		if m.audioPlayer != nil {
			m.audioPlayer.Close()
		}
		if m.audioCapturer != nil {
			m.audioCapturer.Close()
		}
		return m, tea.Quit

	case "tab":
		// Cycle focus
		m.focus = (m.focus + 1) % 3
		return m, nil
	}

	// 2. Input Focus Priority
	// If typing, ignore all other shortcuts except the absolute globals above
	if m.focus == FocusInput {
		return m.handleInputKeys(msg)
	}

	// 3. Global Shortcuts (Only when NOT in Input)
	switch msg.String() {
	case "q":
		// Quit (same as ctrl+c)
		if m.client != nil {
			m.client.Disconnect()
		}
		if m.audioPlayer != nil {
			m.audioPlayer.Close()
		}
		if m.audioCapturer != nil {
			m.audioCapturer.Close()
		}
		return m, tea.Quit

	case "m", "M":
		// Toggle mute
		m.isMuted = !m.isMuted
		if m.audioPlayer != nil {
			m.audioPlayer.SetMuted(m.isMuted)
		}
		return m, nil

	case "+", "=":
		// Increase volume
		m.playbackVol += 10
		if m.playbackVol > 100 {
			m.playbackVol = 100
		}
		if m.audioPlayer != nil {
			m.audioPlayer.SetVolume(float32(m.playbackVol) / 100.0)
		}
		return m, nil

	case "-", "_":
		// Decrease volume
		m.playbackVol -= 10
		if m.playbackVol < 0 {
			m.playbackVol = 0
		}
		if m.audioPlayer != nil {
			m.audioPlayer.SetVolume(float32(m.playbackVol) / 100.0)
		}
		return m, nil

	case "v", "V":
		// Toggle voice (PTT)
		m.isPTT = !m.isPTT
		if m.isPTT {
			if m.audioCapturer != nil {
				m.audioCapturer.Start()
			}
			m.addLog("🎤 Transmitting...")
		} else {
			if m.audioCapturer != nil {
				m.audioCapturer.Stop()
			}
			m.addLog("🎤 Stopped transmitting")
		}
		return m, nil

	case "l", "L":
		// Toggle Log/Chat view
		m.showLog = !m.showLog
		return m, nil
	}

	// Focus-specific keys
	switch m.focus {
	case FocusChannels:
		return m.handleChannelKeys(msg)
	case FocusInput:
		return m.handleInputKeys(msg)
	case FocusChat:
		return m.handleChatKeys(msg)
	}
	return m, nil
}

func (m *Model) handleChatKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
	return m, nil
}

func (m *Model) handleChannelKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
	switch msg.String() {
	case "up", "k":
		if m.selectedIdx > 0 {
			m.selectedIdx--
		}
	case "down", "j":
		if m.selectedIdx < len(m.channels)-1 {
			m.selectedIdx++
		}
	case "enter":
		// Join selected channel
		if m.selectedIdx < len(m.channels) && m.client != nil {
			ch := m.channels[m.selectedIdx]
			m.addLog("Attempting to join channel: %s (ID=%d)", ch.Name, ch.ID)
			err := m.client.JoinChannel(ch.ID)
			if err != nil {
				m.addLog("Error joining channel: %v", err)
			}
		}
	}
	return m, nil
}

func (m *Model) handleInputKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
	switch msg.String() {
	case "enter":
		if m.inputText != "" && m.client != nil {
			m.client.SendChannelMessage(m.inputText)
			m.inputText = ""
		}
	case "esc":
		m.focus = FocusChannels
		m.inputText = ""
	case "backspace":
		if len(m.inputText) > 0 {
			// Handle UTF-8 backspace properly
			runes := []rune(m.inputText)
			if len(runes) > 0 {
				m.inputText = string(runes[:len(runes)-1])
			}
		}
	default:
		// Add character to input
		// Allow Runes (including multi-byte like ñ) and Space
		// Filter out special keys that might send description strings (like "alt+") by ensuring only 1 rune
		if msg.Type == tea.KeyRunes {
			runes := []rune(msg.String())
			if len(runes) == 1 {
				m.inputText += string(runes)
			}
		} else if msg.Type == tea.KeySpace {
			m.inputText += " "
		}
	}
	return m, nil
}

// View renders the UI
func (m *Model) View() string {
	if m.width == 0 {
		return "Loading..."
	}

	// Styles
	// Layout: header(1) + panels + input(3) + help(1) = header + panels + 4
	// panels should be height - 5 (1 for header, 3 for input with border, 1 for help)
	panelHeight := m.height - 7

	// Calculate explicit widths to fit exactly (Width is content width)
	// Box Width = Content + 2 (Border) + 2 (Padding) = Content + 4
	// We want LeftBox + RightBox = m.width
	leftBoxWidth := m.width / 4
	rightBoxWidth := m.width - leftBoxWidth

	channelPanelStyle := lipgloss.NewStyle().
		Border(lipgloss.RoundedBorder()).
		BorderForeground(lipgloss.Color("63")).
		Padding(0, 1).
		Width(leftBoxWidth - 4).
		Height(panelHeight)

	chatPanelStyle := lipgloss.NewStyle().
		Border(lipgloss.RoundedBorder()).
		BorderForeground(lipgloss.Color("63")).
		Padding(0, 1).
		Width(rightBoxWidth - 4).
		Height(panelHeight)

	inputStyle := lipgloss.NewStyle().
		Border(lipgloss.NormalBorder()).
		BorderForeground(lipgloss.Color("63")).
		Padding(0, 1).
		Width(m.width - 6)

	// Header with status bar
	header := m.renderStatusBar()

	// Channel panel
	channelContent := m.renderChannels()
	if m.focus == FocusChannels {
		channelPanelStyle = channelPanelStyle.BorderForeground(lipgloss.Color("212"))
	}
	channelPanel := channelPanelStyle.Render(channelContent)

	// Right panel (Chat or Log)
	var rightPanel string

	if m.showLog {
		// Log View
		// Use chatPanelStyle default but add focus logic
		logStyle := chatPanelStyle.Copy()

		if m.focus == FocusChat {
			logStyle = logStyle.BorderForeground(lipgloss.Color("212"))
		}

		// Limit to last N messages log to fit panel
		// Panel height is m.height - 7, padding reduces it more
		maxLines := m.height - 11
		if maxLines < 1 {
			maxLines = 1
		}

		start := 0
		if len(m.logMessages) > maxLines {
			start = len(m.logMessages) - maxLines
		}

		// Calculate available width for text
		textWidth := (rightBoxWidth - 4) - 2 // Content width - margin
		if textWidth < 10 {
			textWidth = 10
		}

		textStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
		var lines []string

		for _, msg := range m.logMessages[start:] {
			// Sanitize: remove newlines/returns to prevent double spacing
			msg = strings.ReplaceAll(msg, "\n", " ")
			msg = strings.ReplaceAll(msg, "\r", "")

			// Truncate simplisticly to prevent wrapping issues if too long
			if len(msg) > textWidth {
				msg = msg[:textWidth-3] + "..."
			}
			lines = append(lines, textStyle.Render(msg))
		}

		rightPanel = logStyle.Render(strings.Join(lines, "\n"))
	} else {
		// Chat View
		chatContent := m.renderChat()
		if m.focus == FocusChat {
			chatPanelStyle = chatPanelStyle.BorderForeground(lipgloss.Color("212"))
		}
		rightPanel = chatPanelStyle.Render(chatContent)
	}

	// Input
	inputContent := "> " + m.inputText
	if m.focus == FocusInput {
		inputStyle = inputStyle.BorderForeground(lipgloss.Color("212"))
		inputContent += "█"
	}
	input := inputStyle.Render(inputContent)

	// Footer help
	logHelp := "L log"
	if m.showLog {
		logHelp = "L chat"
	}
	help := lipgloss.NewStyle().Faint(true).Render(fmt.Sprintf("↑↓ navigate │ Enter join │ Tab switch │ %s │ V talk │ M mute │ +/- vol │ q quit", logHelp))

	// Combine panels
	panels := lipgloss.JoinHorizontal(lipgloss.Top, channelPanel, rightPanel)

	return lipgloss.JoinVertical(lipgloss.Left,
		header,
		panels,
		input,
		help,
	)
}

// renderStatusBar renders the top status bar with ping, volume, and mic level
func (m *Model) renderStatusBar() string {
	headerStyle := lipgloss.NewStyle().
		Bold(true).
		Foreground(lipgloss.Color("229")).
		Background(lipgloss.Color("57")).
		Width(m.width)

	if !m.connected {
		return headerStyle.Render("[ Connecting... ]")
	}

	// Left: Server name (add padding manually)
	leftPart := " " + m.serverName

	// Center: Ping
	ping := 0.0
	if m.client != nil {
		ping = m.client.GetPing()
	}
	centerPart := fmt.Sprintf("PING: %.0fms", ping)

	// Right: Volume and Mic (add padding manually)
	volBar := audio.LevelToBar(m.playbackVol, 6)
	muteIcon := "VOL"
	if m.isMuted {
		muteIcon = "MUTE"
	}
	volPart := fmt.Sprintf("%s:%s%d%%", muteIcon, volBar, m.playbackVol)

	micBar := audio.LevelToBar(m.micLevel, 6)
	pttIcon := "MIC"
	if m.isPTT {
		pttIcon = "*TX*"
	}
	micPart := fmt.Sprintf("%s:%s", pttIcon, micBar)

	rightPart := fmt.Sprintf("%s | %s ", volPart, micPart)

	// Calculate spacing for centered ping
	totalWidth := m.width
	if totalWidth < 10 {
		return ""
	}
	leftLen := lipgloss.Width(leftPart)
	centerLen := lipgloss.Width(centerPart)
	rightLen := lipgloss.Width(rightPart)

	// Calculate spaces needed
	leftSpace := (totalWidth - leftLen - centerLen - rightLen) / 2
	rightSpace := totalWidth - leftLen - centerLen - rightLen - leftSpace

	if leftSpace < 1 {
		leftSpace = 1
	}
	if rightSpace < 1 {
		rightSpace = 1
	}

	// Build the status line
	spaces := func(n int) string {
		if n <= 0 {
			return ""
		}
		s := ""
		for i := 0; i < n; i++ {
			s += " "
		}
		return s
	}

	status := leftPart + spaces(leftSpace) + centerPart + spaces(rightSpace) + rightPart

	return headerStyle.Render(status)
}

// Regex for TeamSpeak spacers: [spacer0], [cspacer], [*spacer], etc.
var spacerRegex = regexp.MustCompile(`^\[([*cZr]?spacer[\w\d]*)\](.*)`)

func (m *Model) renderChannels() string {
	if len(m.channels) == 0 {
		return "No channels..."
	}

	var lines []string
	lines = append(lines, lipgloss.NewStyle().Bold(true).Render("CHANNELS"))
	lines = append(lines, "")

	for i, ch := range m.channels {
		// Indentation based on depth
		indent := strings.Repeat("  ", ch.Depth)
		prefix := indent + "  "
		if i == m.selectedIdx {
			prefix = indent + "► "
		}

		style := lipgloss.NewStyle()
		if i == m.selectedIdx {
			style = style.Bold(true).Foreground(lipgloss.Color("212"))
		}

		// Check for spacer
		displayName := ch.Name
		matches := spacerRegex.FindStringSubmatch(ch.Name)
		if len(matches) > 0 {
			tag := matches[1]     // e.g. "spacer0", "cspacer", "*spacer"
			content := matches[2] // e.g. "---", "Section"

			// Calculate effective width: width/4 - 6 (border+padding)
			width := (m.width / 4) - 6
			if width < 0 {
				width = 0
			}

			if strings.HasPrefix(tag, "*") {
				// Repeat content
				if len(content) > 0 {
					count := width / len(content)
					if count > 0 {
						displayName = strings.Repeat(content, count+1)[:width]
					}
				} else {
					displayName = strings.Repeat("-", width)
				}
			} else if strings.HasPrefix(tag, "c") {
				// Center content
				if len(content) > len(displayName) { // If parsed content is safer?
					// No, just align content
					gap := (width - len(content)) / 2
					if gap > 0 {
						displayName = strings.Repeat(" ", gap) + content
					} else {
						displayName = content
					}
				} else {
					// Fallback if measurement is tricky, just show content
					// Actually, let's try to center strictly
					gap := (width - len(content)) / 2
					if gap > 0 {
						displayName = strings.Repeat(" ", gap) + content
					} else {
						displayName = content
					}
				}
			} else {
				// Standard spacer (left align)
				displayName = content
			}
		}

		lines = append(lines, style.Render(prefix+displayName))

		// Show users in channel
		for _, user := range ch.Users {
			userPrefix := "    └─ "
			userStyle := lipgloss.NewStyle().Faint(true)

			// Talking users get bright green color and speaker icon
			if user.Talking {
				userStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("46")) // Bright green
				userPrefix = "    └─ 🔊 "
			} else if user.IsMe {
				userStyle = userStyle.Foreground(lipgloss.Color("82"))
				userPrefix = "    └─ ► "
			}

			lines = append(lines, userStyle.Render(userPrefix+user.Nickname))
		}
	}

	return lipgloss.JoinVertical(lipgloss.Left, lines...)
}

func (m *Model) renderChat() string {
	var lines []string

	if len(m.chatMessages) == 0 {
		lines = append(lines, lipgloss.NewStyle().Faint(true).Render("No chat messages yet..."))
	} else {
		// Limit messages to fit panel
		// Panel height is roughly m.height - 7
		maxLines := m.height - 9
		if maxLines < 1 {
			maxLines = 1
		}

		start := 0
		if len(m.chatMessages) > maxLines {
			start = len(m.chatMessages) - maxLines
		}

		for _, msg := range m.chatMessages[start:] {
			prefix := msg.Time.Format("15:04") + " " + msg.Sender + ": "
			content := msg.Content

			// Simple wrapping logic could go here, but for now simple truncation/display
			line := lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Render(prefix) + content
			lines = append(lines, line)
		}
	}

	// Fill remaining lines with empty space to maintain stable layout
	for len(lines) < m.height-9 {
		lines = append(lines, "")
	}

	return lipgloss.JoinVertical(lipgloss.Left, lines...)
}
