Editor/AllChatWindow.cs
using Editor;
using Sandbox;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace EditorAllChat;

[Dock( "Editor", "All Chat", "chat" )]
public class AllChatWindow : Widget
{
	public static string API_URL => "http://allchat.carsonk.net:2222";
	public static string WEBSOCKET_URL => "ws://allchat.carsonk.net:2222";
	public static string ADMIN_PASSWORD = "";

	ScrollArea ScrollArea;
	LineEdit LineEdit;
	MessageObject LastMessage;

	WebSocket WebSocket;
	CancellationTokenSource CancellationTokenSource;

	public AllChatWindow( Widget parent ) : base( parent )
	{
		Layout = Layout.Column();
		ADMIN_PASSWORD = EditorCookie.GetString( "allchat_admin_password", "" );

		Width = 300;
		Height = 450;

		ScrollArea = Layout.Add( new ScrollArea( this ) );
		ScrollArea.Canvas = new Widget();
		ScrollArea.Canvas.Layout = Layout.Column();
		ScrollArea.Canvas.Layout.Spacing = 2;
		ScrollArea.Canvas.Layout.Margin = new Sandbox.UI.Margin( 0, 0, 0, 8 );
		ScrollArea.Canvas.Layout.AddStretchCell();

		LineEdit = Layout.Add( new LineEdit() );
		LineEdit.PlaceholderText = "Type a message and press Enter...";
		LineEdit.ReturnPressed += SendMessage;

		// Button to open additional chat window for testing
		//var button = Layout.Add( new Button( "Open Another Chat Widget" ) );
		//button.Clicked += () =>
		//{
		//	var newWindow = new AllChatWindow( null );
		//	newWindow.Show();
		//};

		Init();
	}

	async void Init()
	{
		CancellationTokenSource = new CancellationTokenSource();
		WebSocket = new WebSocket();
		WebSocket.OnMessageReceived += OnWebsocketMessage;
		await WebSocket.Connect( WEBSOCKET_URL, CancellationTokenSource.Token );
	}

	void OnWebsocketMessage( string message )
	{
		var eventData = Json.Deserialize<StreamObject>( message );

		switch ( eventData.Type )
		{
			case "connected":

				break;

			case "new_message":
				var newMsg = eventData.Data.Deserialize<MessageObject>();
				if ( newMsg is not null )
				{
					AddMessage( newMsg );
				}
				break;

			case "message_deleted":
				var deletedId = eventData.Data["id"].ToString();
				foreach ( var child in ScrollArea.Canvas.Children )
				{
					if ( child is MessageWidget msgWidget && msgWidget?.Message?.IdString == deletedId )
					{
						msgWidget?.Destroy();
						break;
					}
				}
				break;

			case "messages_cleared":
				foreach ( var child in ScrollArea.Canvas.Children )
				{
					child?.Destroy();
				}
				break;
			case "message_history":
				var jsonArray = eventData.Data.AsArray();
				if ( jsonArray is not null )
				{
					foreach ( var item in jsonArray )
					{
						var msg = item.Deserialize<MessageObject>();
						if ( msg is not null )
						{
							AddMessage( msg );
						}
					}
				}
				break;
		}
	}

	public void Disconnect()
	{
		CancellationTokenSource?.Cancel();
		WebSocket?.Dispose();
		WebSocket = null;
	}

	async void SendMessage()
	{
		if ( string.IsNullOrEmpty( LineEdit.Text ) )
			return;

		var msg = LineEdit.Text;
		LineEdit.Text = "";

		var sent = new SentMessage
		{
			Message = msg,
			SteamId = Game.SteamId.ValueUnsigned
		};
		var response = await Http.RequestJsonAsync<MessageResponse>( API_URL + "/message", "POST", Http.CreateJsonContent( sent ) );
		//if ( response?.Success == true )
		//{
		//	AddMessage( response.Data );
		//}
	}

	public async void AddMessage( MessageObject message )
	{
		// If the new timestamp is on a different day than the last message, add a day header
		if ( LastMessage == null || LastMessage.Timestamp.Date != message.Timestamp.Date )
		{
			ScrollArea.Canvas.Layout.Add( new DayHeader( message.Timestamp.Date ) );
		}

		var fullyScrolled = (ScrollArea.VerticalScrollbar.SliderPosition >= ScrollArea.VerticalScrollbar.Maximum - 1);
		var msgWidget = new MessageWidget( message );
		ScrollArea.Canvas.Layout.Add( msgWidget );
		LastMessage = message;

		await Task.Delay( 25 );
		if ( fullyScrolled || message.SteamId == Game.SteamId )
		{
			ScrollArea.MakeVisible( msgWidget );
		}
	}

	protected override bool OnClose()
	{
		Disconnect();
		return base.OnClose();
	}

	public override void OnDestroyed()
	{
		Disconnect();
		base.OnDestroyed();
	}

	[Shortcut( "gameObject.align-to-view", "CTRL+SHIFT+F" )]
	void OpenAdminPrompt()
	{
		var dialog = new Dialog();
		dialog.Layout = Layout.Column();
		dialog.Window.Title = "Admin Login";
		dialog.Window.Width = 300;
		dialog.Window.Height = 150;

		// Prompt for admin password

		var passwordInput = dialog.Layout.Add( new LineEdit() );
		passwordInput.PlaceholderText = "Admin Password";

		// Submit Button

		var submitButton = dialog.Layout.Add( new Button( "Submit" ) );
		submitButton.Clicked += () =>
		{
			ADMIN_PASSWORD = passwordInput.Text;
			EditorCookie.SetString( "allchat_admin_password", ADMIN_PASSWORD );
			dialog?.Close();
		};

		dialog.Show();
	}
}