using System.Collections.ObjectModel;
using EchoHub.Client.Themes;
using EchoHub.Core.DTOs;
using EchoHub.Core.Models;
using Terminal.Gui.App;
using Terminal.Gui.Configuration;
using Terminal.Gui.Input;
using Terminal.Gui.ViewBase;
using Terminal.Gui.Views;
using Attribute = Terminal.Gui.Drawing.Attribute;
namespace EchoHub.Client.UI;
///
/// Main Terminal.Gui window for the EchoHub chat client.
///
public sealed class MainWindow : Runnable
{
private readonly IApplication _app;
private readonly ListView _channelList;
private readonly ListView _messageList;
private readonly TextView _inputField;
private readonly FrameView _chatFrame;
private readonly Label _statusLabel;
private readonly Label _topicLabel;
private MenuBar _menuBar;
// Cached Key constants — compare via .KeyCode to avoid Key.Equals (which also checks Handled)
private static readonly Key EnterKey = Key.Enter;
private static readonly Key AltEnterKey = Key.Enter.WithAlt;
private static readonly Key CtrlCKey = Key.C.WithCtrl;
private static readonly Key TabKey = Key.Tab;
// Available slash commands for Tab autocomplete
private static readonly string[] SlashCommands =
[
"/status", "/nick", "/color", "/theme", "/send",
"/profile", "/servers", "/join", "/leave", "/topic",
"/users", "/quit", "/help"
];
private readonly List _channelNames = [];
private readonly Dictionary> _channelMessages = [];
private readonly Dictionary _channelUnread = [];
private readonly Dictionary _channelTopics = [];
private string _currentChannel = string.Empty;
private string _currentUser = string.Empty;
///
/// Fired when the user selects a channel. Parameter is the channel name.
///
public event Action? OnChannelSelected;
///
/// Fired when the user presses Enter in the input field. Parameters: channel name, message content.
///
public event Action? OnMessageSubmitted;
///
/// Fired when the user requests to connect via the menu.
///
public event Action? OnConnectRequested;
///
/// Fired when the user requests to disconnect via the menu.
///
public event Action? OnDisconnectRequested;
///
/// Fired when the user requests to open their profile panel.
///
public event Action? OnProfileRequested;
///
/// Fired when the user requests to set their status.
///
public event Action? OnStatusRequested;
///
/// Fired when the user selects a theme from the menu. Parameter is the theme name.
///
public event Action? OnThemeSelected;
///
/// Fired when the user requests to view saved servers.
///
public event Action? OnSavedServersRequested;
///
/// Fired when the user requests to create a new channel.
///
public event Action? OnCreateChannelRequested;
public MainWindow(IApplication app)
{
_app = app;
Arrangement = ViewArrangement.Fixed;
// Menu bar at the top
_menuBar = BuildMenuBar();
Add(_menuBar);
// Left panel - channels
var channelsFrame = new FrameView
{
Title = "Channels",
X = 0,
Y = 1, // below menu bar
Width = 25,
Height = Dim.Fill(1) // leave room for status bar
};
_channelList = new ListView
{
X = 0,
Y = 0,
Width = Dim.Fill(),
Height = Dim.Fill()
};
_channelList.SetSource(new ObservableCollection(_channelNames));
_channelList.ValueChanged += OnChannelListSelectionChanged;
channelsFrame.Add(_channelList);
Add(channelsFrame);
// Topic bar — sits above the chat frame in the right column
_topicLabel = new Label
{
Text = "",
X = 25,
Y = 1,
Width = Dim.Fill(),
Height = 1,
Visible = false
};
Add(_topicLabel);
// Center panel - messages
_chatFrame = new FrameView
{
Title = "Chat",
X = 25,
Y = 1, // below menu bar (shifts to 2 when topic is visible)
Width = Dim.Fill(),
Height = Dim.Fill(6) // leave room for input area and status bar
};
_messageList = new ListView
{
X = 0,
Y = 0,
Width = Dim.Fill(),
Height = Dim.Fill()
};
_messageList.Source = new ChatListSource();
_chatFrame.Add(_messageList);
Add(_chatFrame);
// Bottom input area
var inputFrame = new FrameView
{
Title = "Message (Enter=send, Tab=autocomplete)",
X = 25,
Y = Pos.Bottom(_chatFrame),
Width = Dim.Fill(),
Height = 5
};
_inputField = new TextView
{
X = 0,
Y = 0,
Width = Dim.Fill(),
Height = Dim.Fill(),
WordWrap = true
};
_inputField.KeyDown += OnInputKeyDown;
inputFrame.Add(_inputField);
Add(inputFrame);
// Status bar at the very bottom
_statusLabel = new Label
{
Text = "Disconnected",
X = 0,
Y = Pos.AnchorEnd(1),
Width = Dim.Fill(),
Height = 1
};
_statusLabel.SetScheme(SchemeManager.GetScheme("Menu"));
Add(_statusLabel);
// Apply our custom color schemes to all views
ApplyColorSchemes();
// Window-level key handling for Ctrl+C (quit)
KeyDown += OnWindowKeyDown;
}
///
/// Applies the currently registered color schemes to all views.
/// Call after theme changes to refresh colors.
///
public void ApplyColorSchemes()
{
var baseScheme = SchemeManager.GetScheme("Base");
var menuScheme = SchemeManager.GetScheme("Menu");
if (baseScheme is not null)
{
this.SetScheme(baseScheme);
// Propagate to all child views that should use the base scheme
foreach (var sub in SubViews)
{
if (sub != _menuBar && sub != _statusLabel && sub != _topicLabel)
sub.SetScheme(baseScheme);
}
}
if (menuScheme is not null)
{
_menuBar.SetScheme(menuScheme);
_statusLabel.SetScheme(menuScheme);
_topicLabel.SetScheme(menuScheme);
}
}
///
/// Builds the menu bar with File, Server, User menus and a theme submenu.
///
private MenuBar BuildMenuBar()
{
// Build theme menu items and prepend them with a separator header
var themeItems = new List