using Noctalk.Shared.Chat.Configuration;
using Noctalk.Shared.Chat.Constants;
using Noctalk.Shared.Chat.Encryption;
using Noctalk_client.Client.Chat;
using Noctalk_client.Client.Components.UI.Conversation;
using Noctalk_client.Client.Conversations;
using Noctalk_client.Client.Images;
using Noctalk_client.Client.Admin;
using Noctalk_client.Client.Networking.Chat;
using Noctalk_client.Client.Networking.Tor;
using Noctalk_client.Client.Runtime;
using Noctalk_client.Client.Server_status;
using Noctalk_client.Client.Sound;
using Noctalk.UI.Components.Button;
using Noctalk.UI.Components.Buttons_left_area;
using Noctalk.UI.Components.Chat;
using Noctalk.UI.Components.Confirmation;
using Noctalk.UI.Windowing;
using System.Text;
using System.Text.Json;
using System.IO;
using System.Net.Http;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Animation;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using ChatMessage = Noctalk.Shared.Chat.Api.Json.Chat_client__to__server.Poll.Message;
using ChatReceive = Noctalk.Shared.Chat.Api.Json.Chat_client__to__server.Poll.Receive;
using SendAndReceiveConversation = Noctalk.Shared.Chat.Api.Json.Chat_client__to__server.Poll.Send__and__receive__conversation;
using SendAndReceiveConversationResult = Noctalk.Shared.Chat.Api.Json.Chat_client__to__server.Poll.Send__and__receive__conversation_result;
using Ellipse = System.Windows.Shapes.Ellipse;

namespace Noctalk_client;

public partial class MainWindow : Window
{
    private static readonly Brush Text_primary = UI__brush(185, 243, 255);
    private static readonly Brush Text_remote = UI__brush(116, 184, 255);
    private static readonly Brush Text_muted = UI__brush(145, 168, 182);
    private static readonly Brush Text_danger = UI__brush(255, 138, 154);
    private static readonly Brush Accent_blue = UI__brush(73, 217, 255);
    private static readonly Brush Border_subtle = UI__brush(18, 62, 93);
    private static readonly Brush Chat_bubble_local = UI__brush(7, 28, 44);
    private static readonly Brush Chat_bubble_remote = UI__brush(18, 16, 45);
    private static readonly Brush Overlay_message_delivery = UI__brush(190, 5, 9, 18);
    private readonly string executableDirectory;
    private readonly Conversation_repository conversationRepository;
    private readonly Chat_poll_client chatPollClient = new();
    private readonly Chat_memory_store chatMemoryStore = new();
    private readonly Outgoing_send_queue outgoingSendQueue = new();
    private readonly DispatcherTimer pollTimer = new() { Interval = TimeSpan.FromSeconds(5) };
    private readonly DispatcherTimer logViewTimer = new() { Interval = TimeSpan.FromSeconds(2) };
    private readonly List<string> queuedLogEntries = [];
    private readonly object queuedLogEntriesLock = new();
    private readonly CancellationTokenSource shutdownCancellationTokenSource = new();
    private readonly SemaphoreSlim torStartupSemaphore = new(1, 1);
    private readonly Client_sound_engine soundEngine;
    private readonly Noctalk_window_icon_handler windowIconHandler;
    private readonly List<Admin.Admin_server_access> adminServerAccesses = [];
    private readonly Dictionary<string, enum__server_connection_status> dictionary__server_connection_status = new(StringComparer.OrdinalIgnoreCase);
    private readonly Dictionary<Guid, enum__conversation_peer_status> dictionary__conversation_peer_status = [];
    private readonly Dictionary<string, DateTime> dictionary__server_next_retry_utc = new(StringComparer.OrdinalIgnoreCase);
    private Tor_client_service? torClientService;
    private Admin_discovery? adminDiscovery;
    private Main_client_config mainClientConfig = new();
    private Conversation_config? currentConversationConfig;
    private Noctalk_conversation_list_item? currentConversationListComponent;
    private long messageCounter;
    private bool isPolling;
    private bool isClosing;
    private bool isShowingLog;
    private bool isShowingAdmin;
    private bool isClientOnline;
    private bool bClient_icon_first_check_resolved;
    private double imageZoomScale = 1.0;
    private bool imageZoomIsDragging;
    private bool imageZoomHasDragged;
    private Point imageZoomDragStartPoint;
    private Point imageZoomDragStartTranslation;
    private Admin.Admin_panel? adminPanel;
    private int? currentTorPort;
    private DateTime lastSuccessfulServerResponseUtc = DateTime.UtcNow;
    private DateTime nextAllowedTorRestartUtc = DateTime.MinValue;
    private int torRestartBackoffIndex;
    private bool bTorRestartInProgress;

    public MainWindow()
    {
        InitializeComponent();
        Title = "Noctalk - Connecting...";

        executableDirectory = AppContext.BaseDirectory;
        windowIconHandler = new Noctalk_window_icon_handler(this, Noctalk_window_icon_app.Client);
        windowIconHandler.Window_icon__set(Noctalk_window_icon_state.Initializing);
        soundEngine = new Client_sound_engine(Noctalk_client__log__append);
        conversationRepository = new Conversation_repository(executableDirectory);
        adminDiscovery = new Admin_discovery(conversationRepository, Noctalk_client__admin_discovery__ensure_tor_ready_async, () => mainClientConfig.Client__local_tor__port, Noctalk_client__log__append, Noctalk_client__admin_discovery__update_received);

        pollTimer.Tick += Poll_timer__tick;
        logViewTimer.Tick += Log_view_timer__tick;
        Closing += MainWindow__closing;
    }

    public async Task Initialize_client_async()
    {
        await Dispatcher.Yield();

        try
        {
            Noctalk_client__startup__ensure_runtime_folders(executableDirectory);
            torClientService = new Tor_client_service(executableDirectory, Noctalk_client__log__append);
            mainClientConfig = Noctalk_client__startup__ensure_main_config(executableDirectory);
            Noctalk_client__ui__apply_config(mainClientConfig);
            Noctalk_client__ui__load_conversations();
            Noctalk_client__ui__set_online_state(false);
            isShowingLog = true;
            Noctalk_client__ui__apply_view_state();
            Noctalk_client__conversation__auto_select_on_startup();
            adminDiscovery?.Discover__admin_rights();
        }
        catch (Exception exception)
        {
            Noctalk_client__log__append($"Initialization failed: {exception.Message}");
            Noctalk_client__ui__set_online_state(false);
            Noctalk_client__window_icon__set_resolved_offline();
            isShowingLog = true;
            Noctalk_client__ui__apply_view_state();
        }
    }

    private static void Noctalk_client__startup__ensure_runtime_folders(string The_executable_directory)
    {
        string[] relativeDirectories =
        [
            Path.Combine("Data"),
            Path.Combine("Data", "Chats"),
            Path.Combine("darknet", "tor"),
            Path.Combine("darknet", "data")
        ];

        foreach (string relativeDirectory in relativeDirectories)
        {
            Directory.CreateDirectory(Path.Combine(The_executable_directory, relativeDirectory));
        }
    }

    private static Main_client_config Noctalk_client__startup__ensure_main_config(string The_executable_directory)
    {
        const string relativeConfigPath = "Data/config.cfg";
        string configPath = Path.Combine(The_executable_directory, relativeConfigPath);
        bool configExists = File.Exists(configPath);
        bool configHasLocalTorPort = false;
        bool configChanged = false;
        Main_client_config mainClientConfig = Base64_json_file.Read__from__relative_path<Main_client_config>(The_executable_directory, relativeConfigPath);

        if (configExists)
        {
            try
            {
                string json = Encoding.UTF8.GetString(Convert.FromBase64String(File.ReadAllText(configPath, Encoding.UTF8)));
                using JsonDocument document = JsonDocument.Parse(json);
                configHasLocalTorPort = document.RootElement.TryGetProperty(nameof(Main_client_config.Client__local_tor__port), out _);
            }
            catch
            {
                configHasLocalTorPort = false;
            }
        }

        if (!configHasLocalTorPort)
        {
            int? legacyPort = Noctalk_client__startup__read_legacy_conversation_local_tor_port(The_executable_directory);
            if (legacyPort.HasValue)
            {
                mainClientConfig.Client__local_tor__port = legacyPort.Value;
                configChanged = true;
            }
        }

        if (mainClientConfig.Client__local_tor__port == 65190)
        {
            mainClientConfig.Client__local_tor__port = 65191;
            configChanged = true;
        }

        if (!configExists || !configHasLocalTorPort || configChanged)
        {
            Base64_json_file.Write__to__relative_path(The_executable_directory, relativeConfigPath, mainClientConfig);
        }

        return mainClientConfig;
    }

    private static int? Noctalk_client__startup__read_legacy_conversation_local_tor_port(string The_executable_directory)
    {
        string chatsDirectory = Path.Combine(The_executable_directory, "Data", "Chats");
        if (!Directory.Exists(chatsDirectory))
        {
            return null;
        }

        foreach (string configPath in Directory.EnumerateFiles(chatsDirectory, "config.cfg", SearchOption.AllDirectories))
        {
            try
            {
                string json = Encoding.UTF8.GetString(Convert.FromBase64String(File.ReadAllText(configPath, Encoding.UTF8)));
                using JsonDocument document = JsonDocument.Parse(json);
                if (document.RootElement.TryGetProperty("Client__local_tor__port", out JsonElement portElement) && portElement.TryGetInt32(out int port) && port is > 0 and <= 65535)
                {
                    return port;
                }
            }
            catch
            {
                // Ignore invalid legacy configs; normal conversation loading handles bad files separately.
            }
        }

        return null;
    }

    private void Noctalk_client__ui__apply_config(Main_client_config The_main_client_config)
    {
        Admin_button.Visibility = Visibility.Collapsed;
        soundEngine.bSound_on = The_main_client_config.Flag__sound__on;
        Sound_toggle_button.Icon = The_main_client_config.Flag__sound__on ? Noctalk_left_area_button_icon.Sound_on : Noctalk_left_area_button_icon.Sound_off;
    }

    private void Noctalk_client__ui__load_conversations()
    {
        Conversation_list.Children.Clear();

        foreach (Conversation_list_item conversation in conversationRepository.Conversations__load_all())
        {
            Noctalk_conversation_list_item conversationComponent = new(conversation);
            conversationComponent.Conversation_selected += (_, _) => Noctalk_client__conversation__select(conversationComponent);
            conversationComponent.Clear_messages_requested += (_, _) => Noctalk_client__conversation__clear_messages_from_menu(conversationComponent);
            conversationComponent.Delete_requested += (_, _) => Noctalk_client__conversation__delete_from_menu(conversationComponent);
            string serverOnionAddress = conversation.Conversation_config.Server__onion_address.Trim();
            if (!dictionary__server_connection_status.TryGetValue(serverOnionAddress, out enum__server_connection_status serverStatus))
            {
                serverStatus = enum__server_connection_status.Checking;
                dictionary__server_connection_status[serverOnionAddress] = serverStatus;
            }

            Conversation_list.Children.Add(conversationComponent);
            Noctalk_client__conversation_status__refresh(conversationComponent, serverStatus, Noctalk_client__conversation_peer_status__get(conversationComponent));
        }
    }

    private void Noctalk_client__conversation__auto_select_on_startup()
    {
        Noctalk_conversation_list_item? conversationToSelect = null;

        foreach (object item in Conversation_list.Children)
        {
            if (item is not Noctalk_conversation_list_item conversationComponent)
            {
                continue;
            }

            if (mainClientConfig.Current_conversation__guid.HasValue && conversationComponent.Conversation.Conversation_config.conversation__guid == mainClientConfig.Current_conversation__guid.Value)
            {
                conversationToSelect = conversationComponent;
                break;
            }

            conversationToSelect ??= conversationComponent;
        }

        if (conversationToSelect is not null && (mainClientConfig.Current_conversation__guid.HasValue || Conversation_list.Children.Count == 1))
        {
            Noctalk_client__log__append($"Auto-selecting conversation: {conversationToSelect.Conversation}");
            Noctalk_client__conversation__select(conversationToSelect);
        }
    }

    private async void Send_message_area__send_requested(object sender, EventArgs e)
    {
        Chat_send_message_payload payload = Send_message_area.Consume_payload();

        foreach (BitmapSource image in payload.Images)
        {
            await Noctalk_client__message__send_local_image_async(image);
        }

        if (!string.IsNullOrWhiteSpace(payload.Text))
        {
            await Noctalk_client__message__send_local_text_async(payload.Text, false);
        }
    }

    private void Middle_root_grid__size_changed(object sender, SizeChangedEventArgs e)
    {
        Noctalk_client__ui__update_chat_boundary_line();
    }

    private async void Poll_timer__tick(object? sender, EventArgs e)
    {
        await Noctalk_client__send__and__receive_all_servers_async();
    }

    private async void Noctalk_client__conversation__select(Noctalk_conversation_list_item Conversation_component)
    {
        currentConversationListComponent?.UI__conversation_item__set_selected(false);
        currentConversationListComponent = Conversation_component;
        currentConversationListComponent.UI__conversation_item__clear_unread();
        currentConversationListComponent.UI__conversation_item__set_selected(true);

        Conversation_list_item conversationListItem = Conversation_component.Conversation;
        currentConversationConfig = conversationListItem.Conversation_config;
        isShowingLog = false;
        isShowingAdmin = false;
        mainClientConfig.Current_conversation__guid = currentConversationConfig.conversation__guid;
        Base64_json_file.Write__to__relative_path(executableDirectory, "Data/config.cfg", mainClientConfig);
        Message_stack.Children.Clear();
        Noctalk_client__ui__apply_view_state();
        Noctalk_client__conversation__render_memory(currentConversationConfig.conversation__guid);
        Noctalk_client__message_input__focus();
        if (await Noctalk_client__tor__ensure_async())
        {
            pollTimer.Start();
            await Noctalk_client__send__and__receive_all_servers_async();
        }
        else
        {
            Noctalk_client__all_known_servers__mark_unreachable();
        }
    }

    private void Noctalk_client__conversation__clear_messages_from_menu(Noctalk_conversation_list_item Conversation_component)
    {
        if (currentConversationListComponent != Conversation_component)
        {
            Noctalk_client__conversation__select(Conversation_component);
        }

        Noctalk_client__conversation__clear_messages_local_and_remote();
    }

    private void Noctalk_client__conversation__clear_messages_local_and_remote()
    {
        if (currentConversationConfig is null)
        {
            return;
        }

        Message_stack.Children.Clear();
        chatMemoryStore.Chat__messages__clear(currentConversationConfig.conversation__guid);

        ChatMessage encryptedMessage = Local_message_crypto.Chat__encrypt_clear_history(currentConversationConfig.conversation__client__guid, ++messageCounter, currentConversationConfig.conversation__local_encryption__key);
        _ = Noctalk_client__poll__receive_async(encryptedMessage, true);
    }

    private void Noctalk_client__conversation__delete_from_menu(Noctalk_conversation_list_item Conversation_component)
    {
        string nickname = Conversation_component.Conversation.ToString();
        if (!Noctalk_confirmation.Show(this, "Delete conversation", $"Are you sure you want to delete {nickname}? You will not be able to talk to this person again!"))
        {
            return;
        }

        if (!Noctalk_confirmation.Show(this, "Delete conversation", "Are you absolutely sure of this? This operation is irreversible!"))
        {
            return;
        }

        string deletedServerOnionAddress = Conversation_component.Conversation.Conversation_config.Server__onion_address.Trim();
        conversationRepository.Conversations__delete(Conversation_component.Conversation);
        chatMemoryStore.Chat__messages__clear(Conversation_component.Conversation.Conversation_config.conversation__guid);
        dictionary__conversation_peer_status.Remove(Conversation_component.Conversation.Conversation_config.conversation__guid);
        Conversation_list.Children.Remove(Conversation_component);
        Noctalk_client__server_status__remove_if_unused(deletedServerOnionAddress);
        adminDiscovery?.Discover__admin_rights();

        if (currentConversationListComponent == Conversation_component)
        {
            currentConversationListComponent = null;
            currentConversationConfig = null;
            Message_stack.Children.Clear();
            pollTimer.Stop();
            Noctalk_client__ui__apply_view_state();
        }
    }

    private void Noctalk_client__server_status__remove_if_unused(string The_server_onion_address)
    {
        string serverOnionAddress = The_server_onion_address.Trim();
        if (string.IsNullOrWhiteSpace(serverOnionAddress))
        {
            return;
        }

        bool flagServerStillUsed = Conversation_list.Children
            .OfType<Noctalk_conversation_list_item>()
            .Any(The_component => The_component.Conversation.Conversation_config.Server__onion_address.Trim().Equals(serverOnionAddress, StringComparison.OrdinalIgnoreCase));

        if (flagServerStillUsed)
        {
            return;
        }

        dictionary__server_connection_status.Remove(serverOnionAddress);
        dictionary__server_next_retry_utc.Remove(serverOnionAddress);
        Noctalk_client__window_icon__refresh_from_server_states();
    }

    private void Noctalk_client__conversation__render_memory(Guid Conversation_guid, bool Flag__scroll_to_end_after_render = true)
    {
        foreach (Chat_memory_message memoryMessage in chatMemoryStore.Chat__messages__get(Conversation_guid))
        {
            try
            {
                if (memoryMessage.Message_type == Message_type.Text)
                {
                    Noctalk_client__message__append_text(memoryMessage.Message_content, memoryMessage.Flag__local, false, memoryMessage.Message_delivery, false, memoryMessage.Created_utc);
                }
                else if (memoryMessage.Message_type == Message_type.Image)
                {
                    Noctalk_client__message__append_image(Image_message_processor.Images__bitmap_from_jpg_base64(memoryMessage.Message_content), memoryMessage.Flag__local, memoryMessage.Message_content, false, memoryMessage.Message_delivery, false, memoryMessage.Created_utc);
                }
            }
            catch (Exception exception)
            {
                Noctalk_client__log__append($"Could not render stored message: {exception.Message}");
            }
        }

        if (Flag__scroll_to_end_after_render)
        {
            Message_scroll.ScrollToEnd();
        }
    }

    private async Task<bool> Noctalk_client__tor__ensure_async()
    {
        if (torClientService is null)
        {
            return false;
        }

        await torStartupSemaphore.WaitAsync(shutdownCancellationTokenSource.Token);

        try
        {
            if (currentTorPort == mainClientConfig.Client__local_tor__port)
            {
                return true;
            }

            torClientService.Tor__client__stop();
            currentTorPort = null;
            bool started = await torClientService.Tor__client__start_async(mainClientConfig.Client__local_tor__port, shutdownCancellationTokenSource.Token);
            currentTorPort = started ? mainClientConfig.Client__local_tor__port : null;
            Noctalk_client__ui__set_online_state(started);
            return started;
        }
        finally
        {
            torStartupSemaphore.Release();
        }
    }

    private async Task<bool> Noctalk_client__tor__restart_async()
    {
        if (torClientService is null)
        {
            return false;
        }

        await torStartupSemaphore.WaitAsync(shutdownCancellationTokenSource.Token);

        try
        {
            bTorRestartInProgress = true;
            torClientService.Tor__client__stop();
            currentTorPort = null;
            bool started = await torClientService.Tor__client__start_async(mainClientConfig.Client__local_tor__port, shutdownCancellationTokenSource.Token);
            currentTorPort = started ? mainClientConfig.Client__local_tor__port : null;
            Noctalk_client__ui__set_online_state(started);
            return started;
        }
        finally
        {
            bTorRestartInProgress = false;
            torStartupSemaphore.Release();
        }
    }

    private async Task Noctalk_client__tor__restart_if_all_servers_unresponsive_async()
    {
        if (isClosing || bTorRestartInProgress)
        {
            return;
        }

        DateTime nowUtc = DateTime.UtcNow;
        TimeSpan restartDelay = Client_tor_policy.Restart_delay__get(torRestartBackoffIndex);
        if (nowUtc < nextAllowedTorRestartUtc || nowUtc - lastSuccessfulServerResponseUtc < restartDelay)
        {
            return;
        }

        Noctalk_client__log__append($"Restarting client Tor: no server has responded for {restartDelay.TotalMinutes:0} minute(s).");
        torRestartBackoffIndex++;
        nextAllowedTorRestartUtc = nowUtc + Client_tor_policy.Restart_delay__get(torRestartBackoffIndex);
        await Noctalk_client__tor__restart_async();
    }

    private void Noctalk_client__server__mark_reachable(string The_server_onion_address)
    {
        lastSuccessfulServerResponseUtc = DateTime.UtcNow;
        torRestartBackoffIndex = 0;
        nextAllowedTorRestartUtc = DateTime.MinValue;

        if (dictionary__server_connection_status.TryGetValue(The_server_onion_address, out enum__server_connection_status previousStatus) && previousStatus == enum__server_connection_status.Offline)
        {
            Noctalk_client__log__append("Server reachable again.");
        }

        Noctalk_client__server_status__set(The_server_onion_address, enum__server_connection_status.Online);
        dictionary__server_next_retry_utc.Remove(The_server_onion_address);
    }

    private void Noctalk_client__server__mark_unreachable(string The_server_onion_address)
    {
        if (!dictionary__server_connection_status.TryGetValue(The_server_onion_address, out enum__server_connection_status previousStatus) || previousStatus != enum__server_connection_status.Offline)
        {
            Noctalk_client__log__append("Server unreachable.");
        }

        Noctalk_client__server_status__set(The_server_onion_address, enum__server_connection_status.Offline);
        dictionary__server_next_retry_utc[The_server_onion_address] = DateTime.UtcNow.AddMinutes(2);
    }

    private void Noctalk_client__all_known_servers__mark_unreachable()
    {
        List<string> serverOnionAddresses = Conversation_list.Children
            .OfType<Noctalk_conversation_list_item>()
            .Select(The_component => The_component.Conversation.Conversation_config.Server__onion_address.Trim())
            .Where(The_server_onion_address => !string.IsNullOrWhiteSpace(The_server_onion_address))
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .ToList();

        foreach (string serverOnionAddress in serverOnionAddresses)
        {
            Noctalk_client__server__mark_unreachable(serverOnionAddress);
        }
    }

    private void Noctalk_client__server_status__set(string The_server_onion_address, enum__server_connection_status The_status)
    {
        string serverOnionAddress = The_server_onion_address.Trim();
        dictionary__server_connection_status[serverOnionAddress] = The_status;

        foreach (object item in Conversation_list.Children)
        {
            if (item is Noctalk_conversation_list_item conversationComponent && conversationComponent.Conversation.Conversation_config.Server__onion_address.Trim().Equals(serverOnionAddress, StringComparison.OrdinalIgnoreCase))
            {
                Noctalk_client__conversation_status__refresh(conversationComponent, The_status, Noctalk_client__conversation_peer_status__get(conversationComponent));
            }
        }

        Noctalk_client__window_icon__refresh_from_server_states();
    }

    private void Noctalk_client__window_icon__refresh_from_server_states()
    {
        bool flagAnyServerOnline = dictionary__server_connection_status.Values.Any(The_status => The_status == enum__server_connection_status.Online);
        bool flagAnyServerChecking = dictionary__server_connection_status.Values.Any(The_status => The_status == enum__server_connection_status.Checking);

        if (flagAnyServerOnline)
        {
            bClient_icon_first_check_resolved = true;
            Noctalk_client__ui__set_online_state(true);
            windowIconHandler.Window_icon__set(Noctalk_window_icon_state.Online);
            return;
        }

        if (!bClient_icon_first_check_resolved && flagAnyServerChecking)
        {
            windowIconHandler.Window_icon__set(Noctalk_window_icon_state.Initializing);
            return;
        }

        bClient_icon_first_check_resolved = true;
        Noctalk_client__ui__set_online_state(false);
        windowIconHandler.Window_icon__set(Noctalk_window_icon_state.Offline);
    }

    private void Noctalk_client__window_icon__set_resolved_offline()
    {
        bClient_icon_first_check_resolved = true;
        Noctalk_client__ui__set_online_state(false);
        windowIconHandler.Window_icon__set(Noctalk_window_icon_state.Offline);
    }

    private void Noctalk_client__conversation_status__set_peer_presence(Noctalk_conversation_list_item The_conversation_component, bool Flag__peer_online, enum__conversation_display_status? Previous_status = null)
    {
        enum__conversation_display_status previousStatus = Previous_status ?? The_conversation_component.Current_online_status;
        enum__conversation_peer_status peerStatus = Flag__peer_online ? enum__conversation_peer_status.Online : enum__conversation_peer_status.Offline;
        dictionary__conversation_peer_status[The_conversation_component.Conversation.Conversation_config.conversation__guid] = peerStatus;
        enum__server_connection_status serverStatus = Noctalk_client__server_connection_status__get(The_conversation_component.Conversation.Conversation_config.Server__onion_address);
        enum__conversation_display_status newStatus = Noctalk_client__conversation_status__refresh(The_conversation_component, serverStatus, peerStatus);

        if (previousStatus == enum__conversation_display_status.Peer_offline && newStatus == enum__conversation_display_status.Peer_online)
        {
            soundEngine.Sound__play(enum__client_sound.User_online);
        }
    }

    private enum__conversation_display_status Noctalk_client__conversation_status__refresh(Noctalk_conversation_list_item The_conversation_component, enum__server_connection_status Server_status, enum__conversation_peer_status Peer_status)
    {
        enum__conversation_display_status effectiveStatus = Noctalk_client__conversation_status__display__get(Server_status, Peer_status);
        The_conversation_component.UI__conversation_item__set_online_status(effectiveStatus);
        return effectiveStatus;
    }

    private enum__conversation_display_status Noctalk_client__conversation_status__display__get(enum__server_connection_status Server_status, enum__conversation_peer_status Peer_status)
    {
        if (Server_status == enum__server_connection_status.Offline)
        {
            return enum__conversation_display_status.Server_offline;
        }

        if (Server_status == enum__server_connection_status.Checking || Peer_status == enum__conversation_peer_status.Checking)
        {
            return enum__conversation_display_status.Checking;
        }

        return Peer_status == enum__conversation_peer_status.Online ? enum__conversation_display_status.Peer_online : enum__conversation_display_status.Peer_offline;
    }

    private enum__server_connection_status Noctalk_client__server_connection_status__get(string The_server_onion_address)
    {
        string serverOnionAddress = The_server_onion_address.Trim();
        return dictionary__server_connection_status.TryGetValue(serverOnionAddress, out enum__server_connection_status status)
            ? status
            : enum__server_connection_status.Checking;
    }

    private enum__conversation_peer_status Noctalk_client__conversation_peer_status__get(Noctalk_conversation_list_item The_conversation_component)
    {
        Guid conversationGuid = The_conversation_component.Conversation.Conversation_config.conversation__guid;
        return dictionary__conversation_peer_status.TryGetValue(conversationGuid, out enum__conversation_peer_status status)
            ? status
            : enum__conversation_peer_status.Checking;
    }

    private void Noctalk_client__sound__play_receive_batch(bool Flag__received_text, bool Flag__received_image)
    {
        if (Flag__received_image)
        {
            soundEngine.Sound__play(enum__client_sound.Received_image);
            return;
        }

        if (Flag__received_text)
        {
            soundEngine.Sound__play(enum__client_sound.Received_message);
        }
    }

    private bool Noctalk_client__server_status__should_skip_poll(string The_server_onion_address)
    {
        string serverOnionAddress = The_server_onion_address.Trim();
        return dictionary__server_connection_status.TryGetValue(serverOnionAddress, out enum__server_connection_status status)
            && status == enum__server_connection_status.Offline
            && dictionary__server_next_retry_utc.TryGetValue(serverOnionAddress, out DateTime nextRetryUtc)
            && DateTime.UtcNow < nextRetryUtc;
    }

    private async Task<bool> Noctalk_client__admin_discovery__ensure_tor_ready_async(CancellationToken The_cancellation_token)
    {
        if (Dispatcher.CheckAccess())
        {
            return await Noctalk_client__tor__ensure_for_admin_discovery_async(The_cancellation_token);
        }

        Task<bool> ensureTask = await Dispatcher.InvokeAsync(() => Noctalk_client__tor__ensure_for_admin_discovery_async(The_cancellation_token));
        return await ensureTask;
    }

    private void Noctalk_client__admin_discovery__update_received(Admin_discovery_update The_update)
    {
        if (isClosing || Dispatcher.HasShutdownStarted || Dispatcher.HasShutdownFinished)
        {
            return;
        }

        Dispatcher.BeginInvoke(() => Noctalk_client__admin_discovery__apply_update(The_update));
    }

    private void Noctalk_client__admin_discovery__apply_update(Admin_discovery_update The_update)
    {
        if (The_update.Candidate_server_onion_addresses is not null)
        {
            HashSet<string> candidateServers = The_update.Candidate_server_onion_addresses.ToHashSet(StringComparer.OrdinalIgnoreCase);
            adminServerAccesses.RemoveAll(The_access => !candidateServers.Contains(The_access.Server__onion_address));
        }

        if (The_update.Admin_server_access is not null)
        {
            int existingIndex = adminServerAccesses.FindIndex(The_access => The_access.Server__onion_address.Equals(The_update.Admin_server_access.Server__onion_address, StringComparison.OrdinalIgnoreCase));
            if (existingIndex >= 0)
            {
                adminServerAccesses[existingIndex] = The_update.Admin_server_access;
            }
            else
            {
                adminServerAccesses.Add(The_update.Admin_server_access);
            }
        }

        if (The_update.Flag__remove_server_access && !string.IsNullOrWhiteSpace(The_update.Server__onion_address))
        {
            adminServerAccesses.RemoveAll(The_access => The_access.Server__onion_address.Equals(The_update.Server__onion_address, StringComparison.OrdinalIgnoreCase));
        }

        Admin_button.Visibility = adminServerAccesses.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
        if (adminPanel is not null)
        {
            _ = adminPanel.Admin__server_accesses__updated_async(isShowingAdmin);
        }
    }

    private async Task<bool> Noctalk_client__tor__ensure_for_admin_discovery_async(CancellationToken The_cancellation_token)
    {
        Noctalk_client__log__append($"Admin discovery: before Tor startup await for port {mainClientConfig.Client__local_tor__port}.");

        try
        {
            Task<bool> torStartupTask = Noctalk_client__tor__ensure_async();
            Task completedTask = await Task.WhenAny(torStartupTask, Task.Delay(TimeSpan.FromSeconds(60), The_cancellation_token));

            if (completedTask == torStartupTask)
            {
                bool torReady = await torStartupTask;
                Noctalk_client__log__append($"Admin discovery: after Tor startup await for port {mainClientConfig.Client__local_tor__port}; ready={torReady}.");
                return torReady;
            }
        }
        catch (OperationCanceledException) when (The_cancellation_token.IsCancellationRequested)
        {
            return false;
        }

        Noctalk_client__log__append($"Admin discovery: Tor startup timeout after 60 seconds for port {mainClientConfig.Client__local_tor__port}.");
        return false;
    }

    private async Task Noctalk_client__message__send_local_text_async(string? The_message_content = null, bool Flag__clear_send_area = true)
    {
        string messageContent = (The_message_content ?? Send_message_area.Text).Trim();

        if (string.IsNullOrWhiteSpace(messageContent) || currentConversationConfig is null)
        {
            return;
        }

        ChatMessage encryptedMessage = Local_message_crypto.Chat__encrypt_text(messageContent, currentConversationConfig.conversation__client__guid, ++messageCounter, currentConversationConfig.conversation__local_encryption__key);
        Chat_memory_message localMessage = new()
        {
            Message_type = Message_type.Text,
            Message_content = messageContent,
            Flag__local = true,
            Message_delivery = enum__Message_delivery.None,
            Created_utc = DateTime.UtcNow
        };
        chatMemoryStore.Chat__message__add(currentConversationConfig.conversation__guid, localMessage);
        Noctalk_client__message__append_text(messageContent, true, false, localMessage.Message_delivery, true, localMessage.Created_utc);
        if (Flag__clear_send_area)
        {
            Send_message_area.Clear();
        }
        outgoingSendQueue.Enqueue(currentConversationConfig.conversation__guid, localMessage, encryptedMessage);
        _ = Noctalk_client__send__and__receive_all_servers_async();
    }

    private void Noctalk_client__message__append_text(string The_message_content, bool Is__local, bool Flag__store = true, enum__Message_delivery Message_delivery = enum__Message_delivery.None, bool Flag__scroll_to_end = true, DateTime? Created_utc = null)
    {
        DateTime createdUtc = Created_utc ?? DateTime.UtcNow;

        if (Flag__store && currentConversationConfig is not null)
        {
            chatMemoryStore.Chat__message__add(currentConversationConfig.conversation__guid, new Chat_memory_message
            {
                Message_type = Message_type.Text,
                Message_content = The_message_content,
                Flag__local = Is__local,
                Message_delivery = Message_delivery,
                Created_utc = createdUtc
            });
        }

        TextBlock messageTextBlock = new()
        {
            Text = The_message_content,
            TextWrapping = TextWrapping.Wrap,
            FontSize = 14,
            Foreground = Is__local ? Text_primary : Text_remote,
            HorizontalAlignment = HorizontalAlignment.Left,
            VerticalAlignment = VerticalAlignment.Top
        };

        WrapPanel bubbleContent = new()
        {
            MaxWidth = 760
        };
        bubbleContent.Children.Add(messageTextBlock);

        if (Is__local)
        {
            FrameworkElement deliveryElement = Noctalk_client__message__delivery_label(Message_delivery, createdUtc);
            deliveryElement.Margin = new Thickness(8, 7, 0, 0);
            deliveryElement.VerticalAlignment = VerticalAlignment.Bottom;
            bubbleContent.Children.Add(deliveryElement);
        }

        Border messageBubble = new()
        {
            Child = bubbleContent,
            Margin = new Thickness(0, 4, Is__local ? 8 : 0, 4),
            Padding = new Thickness(10),
            MaxWidth = 820,
            Background = Is__local ? Chat_bubble_local : Chat_bubble_remote,
            HorizontalAlignment = Is__local ? HorizontalAlignment.Right : HorizontalAlignment.Left
        };

        if (Is__local)
        {
            Message_stack.Children.Add(messageBubble);
        }
        else
        {
            Message_stack.Children.Add(messageBubble);
        }

        if (Flag__scroll_to_end)
        {
            Message_scroll.ScrollToEnd();
        }
    }

    private void Log_button__click(object sender, RoutedEventArgs e)
    {
        isShowingLog = true;
        isShowingAdmin = false;
        Noctalk_client__conversation__clear_visual_selection();
        Noctalk_client__ui__apply_view_state();
    }

    private void Copy_log_button__click(object sender, RoutedEventArgs e)
    {
        if (string.IsNullOrWhiteSpace(Log_textbox.Text))
        {
            return;
        }

        Clipboard.SetText(Log_textbox.Text);

        if (sender is Noctalk_button button)
        {
            button.Make_button_blink_for_1_second();
        }
    }

    private void Clear_log_button__click(object sender, RoutedEventArgs e)
    {
        lock (queuedLogEntriesLock)
        {
            queuedLogEntries.Clear();
            Log_textbox.Clear();
        }
    }

    private void Sound_toggle_button__click(object sender, RoutedEventArgs e)
    {
        mainClientConfig.Flag__sound__on = !mainClientConfig.Flag__sound__on;
        soundEngine.bSound_on = mainClientConfig.Flag__sound__on;
        Base64_json_file.Write__to__relative_path(executableDirectory, "Data/config.cfg", mainClientConfig);
        Sound_toggle_button.Icon = mainClientConfig.Flag__sound__on ? Noctalk_left_area_button_icon.Sound_on : Noctalk_left_area_button_icon.Sound_off;
    }

    private void Close_button__click(object sender, RoutedEventArgs e)
    {
        Close();
    }

    private void MainWindow__closing(object? sender, System.ComponentModel.CancelEventArgs e)
    {
        isClosing = true;
        shutdownCancellationTokenSource.Cancel();
        pollTimer.Stop();
        logViewTimer.Stop();
        torClientService?.Dispose();
        adminDiscovery?.Dispose();
        chatPollClient.Dispose();
        shutdownCancellationTokenSource.Dispose();
    }

    private async void Admin_button__click(object sender, RoutedEventArgs e)
    {
        if (adminServerAccesses.Count == 0)
        {
            return;
        }

        adminPanel ??= new Admin.Admin_panel(executableDirectory, adminServerAccesses, mainClientConfig.Client__local_tor__port, Noctalk_client__log__append);
        Admin_content.Content = adminPanel;
        isShowingAdmin = true;
        isShowingLog = false;
        Noctalk_client__conversation__clear_visual_selection();
        Noctalk_client__ui__apply_view_state();
        await adminPanel.Admin__activate_async();
    }

    private void Noctalk_client__conversation__clear_visual_selection()
    {
        currentConversationListComponent?.UI__conversation_item__set_selected(false);
        currentConversationListComponent = null;
    }

    private Noctalk_conversation_list_item? Noctalk_client__conversation__find_component(Guid Conversation_guid)
    {
        foreach (object item in Conversation_list.Children)
        {
            if (item is Noctalk_conversation_list_item conversationComponent && conversationComponent.Conversation.Conversation_config.conversation__guid == Conversation_guid)
            {
                return conversationComponent;
            }
        }

        return null;
    }

    private void Window__drag_enter(object sender, DragEventArgs e)
    {
        Noctalk_client__drag_drop__update_overlay(e);
    }

    private void Window__drag_over(object sender, DragEventArgs e)
    {
        Noctalk_client__drag_drop__update_overlay(e);
    }

    private void Window__drag_leave(object sender, DragEventArgs e)
    {
        Drop_overlay.Visibility = Visibility.Collapsed;
    }

    private async void Window__drop(object sender, DragEventArgs e)
    {
        Drop_overlay.Visibility = Visibility.Collapsed;

        if (!e.Data.GetDataPresent(DataFormats.FileDrop))
        {
            return;
        }

        string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
        string? firstConversationConfigPath = files.FirstOrDefault(Noctalk_client__drag_drop__is_conversation_config_path);

        if (firstConversationConfigPath is not null)
        {
            await Noctalk_client__conversation_config__import_async(firstConversationConfigPath);
            return;
        }

        List<string> imagePaths = files.Where(Noctalk_client__drag_drop__is_supported_image_path).ToList();
        if (imagePaths.Count > 0 && !Chat__image_batch_policy.Confirm_if_large_image_batch(this, imagePaths))
        {
            return;
        }

        foreach (string imagePath in imagePaths)
        {
            await Noctalk_client__message__send_local_image_async(imagePath);
        }
    }

    private Task Noctalk_client__message__send_local_image_async(string The_image_path)
    {
        BitmapImage displayImage = new();
        displayImage.BeginInit();
        displayImage.CacheOption = BitmapCacheOption.OnLoad;
        displayImage.UriSource = new Uri(The_image_path);
        displayImage.EndInit();
        displayImage.Freeze();
        return Noctalk_client__message__send_local_image_async(() => Image_message_processor.Images__prepare_jpg_message(The_image_path), displayImage);
    }

    private Task Noctalk_client__message__send_local_image_async(BitmapSource The_bitmap_source)
    {
        return Noctalk_client__message__send_local_image_async(() => Image_message_processor.Images__prepare_jpg_message(The_bitmap_source), The_bitmap_source);
    }

    private Task Noctalk_client__message__send_local_image_async(Func<byte[]> The_prepare_jpg_message, BitmapSource? The_failed_display_image = null)
    {
        if (currentConversationConfig is null)
        {
            Noctalk_client__message__append_text("Select a conversation before sending an image.", false);
            return Task.CompletedTask;
        }

        try
        {
            byte[] jpgBytes = The_prepare_jpg_message();
            string messageContent = Convert.ToBase64String(jpgBytes);
            ChatMessage encryptedMessage = Local_message_crypto.Chat__encrypt_image(messageContent, currentConversationConfig.conversation__client__guid, ++messageCounter, currentConversationConfig.conversation__local_encryption__key);
            Chat_memory_message localMessage = new()
            {
                Message_type = Message_type.Image,
                Message_content = messageContent,
                Flag__local = true,
                Message_delivery = enum__Message_delivery.None,
                Created_utc = DateTime.UtcNow
            };
            chatMemoryStore.Chat__message__add(currentConversationConfig.conversation__guid, localMessage);
            Noctalk_client__message__append_image(Image_message_processor.Images__bitmap_from_jpg_base64(messageContent), true, messageContent, false, localMessage.Message_delivery, true, localMessage.Created_utc);
            outgoingSendQueue.Enqueue(currentConversationConfig.conversation__guid, localMessage, encryptedMessage);
            _ = Noctalk_client__send__and__receive_all_servers_async();

        }
        catch (Exception exception)
        {
            if (The_failed_display_image is not null)
            {
                Noctalk_client__message__append_image(Noctalk_client__image__bitmap_image_from_source(The_failed_display_image), true, null, false, enum__Message_delivery.Failed_to_send, true, DateTime.UtcNow);
            }
            else
            {
                Noctalk_client__log__append($"Image send failed: {exception.Message}");
            }
        }

        return Task.CompletedTask;
    }

    private static BitmapImage Noctalk_client__image__bitmap_image_from_source(BitmapSource The_bitmap_source)
    {
        PngBitmapEncoder encoder = new();
        encoder.Frames.Add(BitmapFrame.Create(The_bitmap_source));
        using MemoryStream memoryStream = new();
        encoder.Save(memoryStream);
        memoryStream.Position = 0;

        BitmapImage bitmapImage = new();
        bitmapImage.BeginInit();
        bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
        bitmapImage.StreamSource = memoryStream;
        bitmapImage.EndInit();
        bitmapImage.Freeze();
        return bitmapImage;
    }

    private void Noctalk_client__drag_drop__update_overlay(DragEventArgs e)
    {
        bool isSupportedDrop = false;

        if (e.Data.GetDataPresent(DataFormats.FileDrop))
        {
            string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
            isSupportedDrop = files.Any(The_path => Noctalk_client__drag_drop__is_supported_image_path(The_path) || Noctalk_client__drag_drop__is_conversation_config_path(The_path));
        }

        e.Effects = isSupportedDrop ? DragDropEffects.Copy : DragDropEffects.None;
        Drop_overlay.Visibility = isSupportedDrop ? Visibility.Visible : Visibility.Collapsed;
        e.Handled = true;
    }

    private async Task Noctalk_client__conversation_config__import_async(string The_path)
    {
        try
        {
            string encodedJson = File.ReadAllText(The_path, Encoding.UTF8);
            string json = Encoding.UTF8.GetString(Convert.FromBase64String(encodedJson));
            Conversation_config conversationConfig = JsonSerializer.Deserialize<Conversation_config>(json) ?? new Conversation_config();
            Noctalk_client__conversation_config__apply_legacy_nickname(conversationConfig, json);

            if (conversationConfig.conversation__guid == Guid.Empty || conversationConfig.conversation__client__guid == Guid.Empty)
            {
                Noctalk_message.Show(this, "Import failed", "Invalid .scon file: missing conversation identifiers.");
                return;
            }

            string conversationDirectory = Path.Combine(executableDirectory, "Data", "Chats", conversationConfig.conversation__guid.ToString());
            Directory.CreateDirectory(conversationDirectory);

            string targetPath = Path.Combine(conversationDirectory, "config.cfg");
            Noctalk_client__conversation_config__write(targetPath, conversationConfig);

            Noctalk_client__ui__load_conversations();
            if (conversationConfig.Admin__guid.HasValue)
            {
                adminDiscovery?.Discover__admin_rights();
            }
        }
        catch (Exception exception)
        {
            Noctalk_message.Show(this, "Import failed", $"Could not import .scon file: {exception.Message}");
        }
    }

    private static bool Noctalk_client__drag_drop__is_conversation_config_path(string The_path)
    {
        return Path.GetExtension(The_path).Equals(File_constants.Conversation_config__extension, StringComparison.OrdinalIgnoreCase);
    }

    private static void Noctalk_client__conversation_config__apply_legacy_nickname(Conversation_config The_conversation_config, string The_json)
    {
        if (!string.IsNullOrWhiteSpace(The_conversation_config.Peer__nickname))
        {
            return;
        }

        using JsonDocument document = JsonDocument.Parse(The_json);
        if (document.RootElement.TryGetProperty("Nickname", out JsonElement legacyNicknameElement))
        {
            The_conversation_config.Peer__nickname = legacyNicknameElement.GetString() ?? string.Empty;
        }
    }

    private static void Noctalk_client__conversation_config__write(string The_target_path, Conversation_config The_conversation_config)
    {
        string json = JsonSerializer.Serialize(The_conversation_config, new JsonSerializerOptions { WriteIndented = true });
        string encodedJson = Convert.ToBase64String(Encoding.UTF8.GetBytes(json));
        File.WriteAllText(The_target_path, encodedJson, Encoding.UTF8);
    }

    private static bool Noctalk_client__drag_drop__is_supported_image_path(string The_path)
    {
        string extension = Path.GetExtension(The_path).ToLowerInvariant();
        return extension is ".jpg" or ".jpeg" or ".png" or ".bmp" or ".gif";
    }

    private async Task<ChatReceive> Noctalk_client__poll__receive_async(ChatMessage? Message, bool Flag__delete__history, IReadOnlyList<Chat_memory_message>? Messages_sent_in_this_poll = null)
    {
        Messages_sent_in_this_poll ??= [];

        if (currentConversationConfig is null || isPolling || isClosing)
        {
            foreach (Chat_memory_message sentMessage in Messages_sent_in_this_poll)
            {
                sentMessage.Message_delivery = enum__Message_delivery.Failed_to_send;
            }

            Noctalk_client__message__rerender_current_conversation_if_visible();
            return new ChatReceive { Flag__success = false, Messages = [] };
        }

        isPolling = true;

        try
        {
            if (Noctalk_client__server_status__should_skip_poll(currentConversationConfig.Server__onion_address))
            {
                return new ChatReceive { Flag__success = false, Messages = [] };
            }

            bool flagConversationHasBeenOpened = !isShowingLog && !isShowingAdmin;
            enum__conversation_display_status? statusBeforePoll = currentConversationListComponent?.Current_online_status;
            ChatReceive receive = await chatPollClient.Chat__poll_async(currentConversationConfig, mainClientConfig.Client__local_tor__port, Message, Flag__delete__history, flagConversationHasBeenOpened, shutdownCancellationTokenSource.Token);
            Noctalk_client__ui__set_online_state(receive.Flag__success);
            if (receive.Flag__success)
            {
                Noctalk_client__server__mark_reachable(currentConversationConfig.Server__onion_address);
                if (currentConversationListComponent is not null)
                {
                    Noctalk_client__conversation_status__set_peer_presence(currentConversationListComponent, receive.Flag__peer_online, statusBeforePoll);
                }
            }
            else
            {
                Noctalk_client__server__mark_unreachable(currentConversationConfig.Server__onion_address);
            }

            foreach (Chat_memory_message sentMessage in Messages_sent_in_this_poll)
            {
                sentMessage.Message_delivery = receive.Flag__success ? enum__Message_delivery.Delivered_to_server : enum__Message_delivery.Failed_to_send;
            }

            chatMemoryStore.Chat__messages__update_outgoing_delivery(currentConversationConfig.conversation__guid, receive.Message_delivery__from_this_client_to_peer, Messages_sent_in_this_poll);
            Noctalk_client__message__rerender_current_conversation_if_visible();

            if (isClosing)
            {
                return receive;
            }

            bool flagReceivedText = false;
            bool flagReceivedImage = false;

            foreach (ChatMessage message in receive.Messages)
            {
                try
                {
                    Inner_message innerMessage = Local_message_crypto.Chat__decrypt(message, currentConversationConfig.conversation__local_encryption__key);

                    if (innerMessage.Sender_client_guid == currentConversationConfig.conversation__client__guid)
                    {
                        continue;
                    }

                    if (isShowingLog || isShowingAdmin)
                    {
                        Guid conversationGuid = currentConversationConfig.conversation__guid;
                        Noctalk_conversation_list_item? conversationComponent = Noctalk_client__conversation__find_component(conversationGuid);

                        if (innerMessage.Message_type == Message_type.Clear_history)
                        {
                            chatMemoryStore.Chat__messages__clear(conversationGuid);
                            conversationComponent?.UI__conversation_item__clear_unread();
                            continue;
                        }

                        chatMemoryStore.Chat__message__add(conversationGuid, new Chat_memory_message
                        {
                            Message_type = innerMessage.Message_type,
                            Message_content = innerMessage.Message_content,
                            Flag__local = false,
                            Created_utc = innerMessage.Created_utc
                        });
                        conversationComponent?.UI__conversation_item__increment_unread();
                        flagReceivedText |= innerMessage.Message_type == Message_type.Text;
                        flagReceivedImage |= innerMessage.Message_type == Message_type.Image;
                        continue;
                    }

                    if (innerMessage.Message_type == Message_type.Text)
                    {
                        Noctalk_client__message__append_text(innerMessage.Message_content, false, true, enum__Message_delivery.None, true, innerMessage.Created_utc);
                        flagReceivedText = true;
                    }
                    else if (innerMessage.Message_type == Message_type.Image)
                    {
                        BitmapImage imageBitmap = Image_message_processor.Images__bitmap_from_jpg_base64(innerMessage.Message_content);
                        Noctalk_client__message__append_image(imageBitmap, false, innerMessage.Message_content, true, enum__Message_delivery.None, true, innerMessage.Created_utc);
                        flagReceivedImage = true;
                    }
                    else if (innerMessage.Message_type == Message_type.Clear_history)
                    {
                        chatMemoryStore.Chat__messages__clear(currentConversationConfig.conversation__guid);
                        Message_stack.Children.Clear();
                    }
                }
                catch (Exception exception)
                {
                    Noctalk_client__message__append_text($"Could not decrypt message: {exception.Message}", false);
                }
            }

            Noctalk_client__sound__play_receive_batch(flagReceivedText, flagReceivedImage);

            return receive;
        }
        catch (Exception exception)
        {
            if (!isClosing)
            {
                Noctalk_client__ui__set_online_state(false);
                if (currentConversationConfig is not null)
                {
                    Noctalk_client__server__mark_unreachable(currentConversationConfig.Server__onion_address);
                }

                Noctalk_client__log__append($"Poll failed: {exception.Message}");
            }

            foreach (Chat_memory_message sentMessage in Messages_sent_in_this_poll)
            {
                sentMessage.Message_delivery = enum__Message_delivery.Failed_to_send;
            }

            Noctalk_client__message__rerender_current_conversation_if_visible();

            return new ChatReceive { Flag__success = false, Messages = [] };
        }
        finally
        {
            isPolling = false;
        }
    }

    private async Task Noctalk_client__send__and__receive_all_servers_async()
    {
        if (isPolling || isClosing)
        {
            return;
        }

        isPolling = true;

        try
        {
            if (!await Noctalk_client__tor__ensure_async())
            {
                Noctalk_client__all_known_servers__mark_unreachable();
                return;
            }

            List<Noctalk_conversation_list_item> conversationComponents = Conversation_list.Children
                .OfType<Noctalk_conversation_list_item>()
                .ToList();

            foreach (IGrouping<string, Noctalk_conversation_list_item> serverGroup in conversationComponents.GroupBy(The_component => The_component.Conversation.Conversation_config.Server__onion_address.Trim(), StringComparer.OrdinalIgnoreCase))
            {
                if (Noctalk_client__server_status__should_skip_poll(serverGroup.Key))
                {
                    continue;
                }

                HashSet<Guid> conversationGuids = serverGroup
                    .Select(The_component => The_component.Conversation.Conversation_config.conversation__guid)
                    .ToHashSet();
                List<Outgoing_send_queue_item> outgoingItems = outgoingSendQueue.Dequeue_for_conversations(conversationGuids);
                Dictionary<Guid, List<Outgoing_send_queue_item>> outgoingByConversation = outgoingItems
                    .GroupBy(The_item => The_item.conversation__guid)
                    .ToDictionary(The_group => The_group.Key, The_group => The_group.OrderBy(The_item => The_item.Creation_order).ToList());
                Dictionary<Guid, enum__conversation_display_status> statusBeforePollByConversation = serverGroup
                    .ToDictionary(The_component => The_component.Conversation.Conversation_config.conversation__guid, The_component => The_component.Current_online_status);

                List<SendAndReceiveConversation> conversations = [];
                foreach (Noctalk_conversation_list_item conversationComponent in serverGroup)
                {
                    Conversation_config config = conversationComponent.Conversation.Conversation_config;
                    outgoingByConversation.TryGetValue(config.conversation__guid, out List<Outgoing_send_queue_item>? queuedItems);
                    bool flagConversationHasBeenOpened = currentConversationConfig?.conversation__guid == config.conversation__guid && !isShowingLog && !isShowingAdmin;
                    conversations.Add(new SendAndReceiveConversation
                    {
                        Conversation_guid = config.conversation__guid,
                        Client_guid = config.conversation__client__guid,
                        Flag__conversation__has_been_opened = flagConversationHasBeenOpened,
                        Messages_to_send = queuedItems?.Select(The_item => The_item.Encrypted_payload).ToList() ?? []
                    });
                }

                try
                {
                    var receive = await chatPollClient.Chat__send__and__receive_async(serverGroup.Key, mainClientConfig.Client__local_tor__port, conversations, shutdownCancellationTokenSource.Token);
                    Noctalk_client__ui__set_online_state(receive.Flag__success);
                    if (receive.Flag__success)
                    {
                        Noctalk_client__server__mark_reachable(serverGroup.Key);
                    }
                    else
                    {
                        Noctalk_client__server__mark_unreachable(serverGroup.Key);
                    }

                    if (!receive.Flag__success)
                    {
                        foreach (Outgoing_send_queue_item item in outgoingItems)
                        {
                            item.Local_message.Message_delivery = enum__Message_delivery.Failed_to_send;
                        }
                    }

                    foreach (SendAndReceiveConversationResult result in receive.Conversations)
                    {
                        Noctalk_conversation_list_item? conversationComponent = Noctalk_client__conversation__find_component(result.Conversation_guid);
                        if (conversationComponent is null)
                        {
                            continue;
                        }

                        Conversation_config config = conversationComponent.Conversation.Conversation_config;
                        outgoingByConversation.TryGetValue(result.Conversation_guid, out List<Outgoing_send_queue_item>? sentItems);
                        List<Chat_memory_message> sentMessages = sentItems?.Select(The_item => The_item.Local_message).ToList() ?? [];

                        foreach (Chat_memory_message sentMessage in sentMessages)
                        {
                            sentMessage.Message_delivery = result.Flag__success ? enum__Message_delivery.Delivered_to_server : enum__Message_delivery.Failed_to_send;
                        }

                        chatMemoryStore.Chat__messages__update_outgoing_delivery(result.Conversation_guid, result.Message_delivery__from_this_client_to_peer, sentMessages);
                        if (receive.Flag__success)
                        {
                            statusBeforePollByConversation.TryGetValue(result.Conversation_guid, out enum__conversation_display_status statusBeforePoll);
                            Noctalk_client__conversation_status__set_peer_presence(conversationComponent, result.Flag__peer_online, statusBeforePoll);
                            Noctalk_client__send__and__receive__process_received_messages(config, conversationComponent, result.Messages);
                        }
                    }

                    if (!receive.Flag__success)
                    {
                        await Noctalk_client__tor__restart_if_all_servers_unresponsive_async();
                    }
                }
                catch (Exception exception)
                {
                    Noctalk_client__ui__set_online_state(false);
                    Noctalk_client__server__mark_unreachable(serverGroup.Key);
                    Noctalk_client__log__append($"Send__and__receive failed: {exception.Message}");
                    foreach (Outgoing_send_queue_item item in outgoingItems)
                    {
                        item.Local_message.Message_delivery = enum__Message_delivery.Failed_to_send;
                    }

                    await Noctalk_client__tor__restart_if_all_servers_unresponsive_async();
                }
            }

            Noctalk_client__message__rerender_current_conversation_if_visible();
        }
        finally
        {
            isPolling = false;
        }
    }

    private void Noctalk_client__send__and__receive__process_received_messages(Conversation_config Config, Noctalk_conversation_list_item Conversation_component, List<ChatMessage> Messages)
    {
        bool flagReceivedText = false;
        bool flagReceivedImage = false;

        foreach (ChatMessage message in Messages)
        {
            try
            {
                Inner_message innerMessage = Local_message_crypto.Chat__decrypt(message, Config.conversation__local_encryption__key);
                if (innerMessage.Sender_client_guid == Config.conversation__client__guid)
                {
                    continue;
                }

                if (innerMessage.Message_type == Message_type.Clear_history)
                {
                    chatMemoryStore.Chat__messages__clear(Config.conversation__guid);
                    Conversation_component.UI__conversation_item__clear_unread();
                    if (currentConversationConfig?.conversation__guid == Config.conversation__guid && !isShowingLog && !isShowingAdmin)
                    {
                        Message_stack.Children.Clear();
                    }
                    continue;
                }

                BitmapImage? imageBitmap = null;
                if (innerMessage.Message_type == Message_type.Image)
                {
                    imageBitmap = Image_message_processor.Images__bitmap_from_jpg_base64(innerMessage.Message_content);
                }

                chatMemoryStore.Chat__message__add(Config.conversation__guid, new Chat_memory_message
                {
                    Message_type = innerMessage.Message_type,
                    Message_content = innerMessage.Message_content,
                    Flag__local = false,
                    Created_utc = innerMessage.Created_utc
                });
                flagReceivedText |= innerMessage.Message_type == Message_type.Text;
                flagReceivedImage |= innerMessage.Message_type == Message_type.Image;

                if (currentConversationConfig?.conversation__guid == Config.conversation__guid && !isShowingLog && !isShowingAdmin)
                {
                    if (imageBitmap is not null)
                    {
                        Noctalk_client__message__append_image(imageBitmap, false, innerMessage.Message_content, false, enum__Message_delivery.None, true, innerMessage.Created_utc);
                    }

                    continue;
                }

                Conversation_component.UI__conversation_item__increment_unread();
            }
            catch (Exception exception)
            {
                Noctalk_client__log__append($"Could not decrypt message: {exception.Message}");
            }
        }

        Noctalk_client__sound__play_receive_batch(flagReceivedText, flagReceivedImage);
    }

    private void Noctalk_client__message__rerender_current_conversation_if_visible()
    {
        if (currentConversationConfig is null || isShowingLog || isShowingAdmin)
        {
            return;
        }

        bool flagWasNearBottom = Noctalk_client__message_scroll__is_near_bottom();
        double previousVerticalOffset = Message_scroll.VerticalOffset;
        Message_stack.Children.Clear();
        Noctalk_client__conversation__render_memory(currentConversationConfig.conversation__guid, false);

        Dispatcher.BeginInvoke(() =>
        {
            if (flagWasNearBottom)
            {
                Message_scroll.ScrollToEnd();
            }
            else
            {
                Message_scroll.ScrollToVerticalOffset(previousVerticalOffset);
            }
        }, DispatcherPriority.Background);
    }

    private bool Noctalk_client__message_scroll__is_near_bottom()
    {
        return Message_scroll.VerticalOffset + Message_scroll.ViewportHeight >= Message_scroll.ExtentHeight - 80;
    }

    private void Noctalk_client__message__append_image(BitmapImage The_bitmap_image, bool Is__local, string? The_image_base64 = null, bool Flag__store = true, enum__Message_delivery Message_delivery = enum__Message_delivery.None, bool Flag__scroll_to_end = true, DateTime? Created_utc = null)
    {
        DateTime createdUtc = Created_utc ?? DateTime.UtcNow;

        if (Flag__store && currentConversationConfig is not null && !string.IsNullOrWhiteSpace(The_image_base64))
        {
            chatMemoryStore.Chat__message__add(currentConversationConfig.conversation__guid, new Chat_memory_message
            {
                Message_type = Message_type.Image,
                Message_content = The_image_base64,
                Flag__local = Is__local,
                Message_delivery = Message_delivery,
                Created_utc = createdUtc
            });
        }

        Chat__message_image_view imageView = new(The_bitmap_image, Math.Min(350, The_bitmap_image.PixelWidth))
        {
            HorizontalAlignment = Is__local ? HorizontalAlignment.Right : HorizontalAlignment.Left
        };
        imageView.Image_clicked += (_, The_image_source) => Noctalk_client__image_zoom__open(The_image_source);

        Grid imageGrid = new();
        imageGrid.Children.Add(imageView);

        if (Is__local)
        {
            Border deliveryOverlay = new()
            {
                Child = Noctalk_client__message__delivery_label(Message_delivery, createdUtc),
                Background = Overlay_message_delivery,
                CornerRadius = new CornerRadius(8),
                Padding = new Thickness(6, 2, 6, 2),
                Margin = new Thickness(0, 0, 6, 5),
                HorizontalAlignment = HorizontalAlignment.Right,
                VerticalAlignment = VerticalAlignment.Bottom,
                IsHitTestVisible = false
            };
            imageGrid.Children.Add(deliveryOverlay);
        }

        Border border = new()
        {
            Child = imageGrid,
            Padding = new Thickness(0),
            Background = Brushes.Transparent,
            BorderThickness = new Thickness(2),
            BorderBrush = Border_subtle,
            Margin = Is__local ? new Thickness(0, 4, 8, 4) : new Thickness(0, 4, 0, 4),
            HorizontalAlignment = Is__local ? HorizontalAlignment.Right : HorizontalAlignment.Left
        };

        if (Is__local)
        {
            StackPanel panel = new()
            {
                HorizontalAlignment = HorizontalAlignment.Right
            };
            panel.Children.Add(border);
            Message_stack.Children.Add(panel);
        }
        else
        {
            Message_stack.Children.Add(border);
        }

        if (Flag__scroll_to_end)
        {
            Message_scroll.ScrollToEnd();
        }
    }

    private void Noctalk_client__image_zoom__open(ImageSource The_image_source)
    {
        imageZoomScale = 1.0;
        imageZoomIsDragging = false;
        imageZoomHasDragged = false;
        Image_zoom_view.Source = The_image_source;
        Image_zoom_view.RenderTransform = Noctalk_client__image_zoom__create_transform(imageZoomScale, 0, 0);
        Image_zoom_overlay.Visibility = Visibility.Visible;
    }

    private void Noctalk_client__image_zoom__close()
    {
        imageZoomIsDragging = false;
        imageZoomHasDragged = false;
        Image_zoom_view.ReleaseMouseCapture();
        Image_zoom_overlay.Visibility = Visibility.Collapsed;
        Image_zoom_view.Source = null;
    }

    private void Image_zoom_overlay__mouse_left_button_down(object sender, MouseButtonEventArgs e)
    {
        Noctalk_client__image_zoom__close();
    }

    private void Image_zoom_view__mouse_left_button_down(object sender, MouseButtonEventArgs e)
    {
        imageZoomIsDragging = true;
        imageZoomHasDragged = false;
        imageZoomDragStartPoint = e.GetPosition(Image_zoom_overlay);
        TranslateTransform translate = Noctalk_client__image_zoom__translation_transform();
        imageZoomDragStartTranslation = new Point(translate.X, translate.Y);
        Image_zoom_view.CaptureMouse();
        e.Handled = true;
    }

    private void Image_zoom_view__mouse_move(object sender, MouseEventArgs e)
    {
        if (!imageZoomIsDragging || imageZoomScale <= 1.0)
        {
            return;
        }

        Point currentPoint = e.GetPosition(Image_zoom_overlay);
        Vector delta = currentPoint - imageZoomDragStartPoint;

        if (Math.Abs(delta.X) > 3 || Math.Abs(delta.Y) > 3)
        {
            imageZoomHasDragged = true;
        }

        TranslateTransform translate = Noctalk_client__image_zoom__translation_transform();
        translate.X = imageZoomDragStartTranslation.X + delta.X;
        translate.Y = imageZoomDragStartTranslation.Y + delta.Y;
    }

    private void Image_zoom_view__mouse_left_button_up(object sender, MouseButtonEventArgs e)
    {
        if (!imageZoomIsDragging)
        {
            return;
        }

        imageZoomIsDragging = false;
        Image_zoom_view.ReleaseMouseCapture();

        if (!imageZoomHasDragged)
        {
            Noctalk_client__image_zoom__close();
        }

        e.Handled = true;
    }

    private void Image_zoom_overlay__mouse_wheel(object sender, MouseWheelEventArgs e)
    {
        if (Image_zoom_overlay.Visibility != Visibility.Visible)
        {
            return;
        }

        double previousScale = imageZoomScale;
        imageZoomScale = Math.Clamp(imageZoomScale * (e.Delta > 0 ? 1.12 : 0.88), 1.0, 6.0);
        ScaleTransform scale = Noctalk_client__image_zoom__scale_transform();
        TranslateTransform translate = Noctalk_client__image_zoom__translation_transform();
        scale.ScaleX = imageZoomScale;
        scale.ScaleY = imageZoomScale;

        if (imageZoomScale == 1.0 || previousScale == 1.0)
        {
            translate.X = 0;
            translate.Y = 0;
        }

        e.Handled = true;
    }

    private static TransformGroup Noctalk_client__image_zoom__create_transform(double The_scale, double The_translate_x, double The_translate_y)
    {
        TransformGroup transformGroup = new();
        transformGroup.Children.Add(new ScaleTransform(The_scale, The_scale));
        transformGroup.Children.Add(new TranslateTransform(The_translate_x, The_translate_y));
        return transformGroup;
    }

    private ScaleTransform Noctalk_client__image_zoom__scale_transform()
    {
        return (ScaleTransform)((TransformGroup)Image_zoom_view.RenderTransform).Children[0];
    }

    private TranslateTransform Noctalk_client__image_zoom__translation_transform()
    {
        return (TranslateTransform)((TransformGroup)Image_zoom_view.RenderTransform).Children[1];
    }

    private static FrameworkElement Noctalk_client__message__delivery_label(enum__Message_delivery Message_delivery, DateTime Created_utc)
    {
        StackPanel panel = new()
        {
            Orientation = Orientation.Horizontal,
            HorizontalAlignment = HorizontalAlignment.Right,
            VerticalAlignment = VerticalAlignment.Center
        };

        panel.Children.Add(new TextBlock
        {
            Text = Created_utc.ToLocalTime().ToString("HH:mm"),
            FontSize = 11,
            Foreground = Text_muted,
            VerticalAlignment = VerticalAlignment.Center,
            Margin = new Thickness(0, 0, 4, 0)
        });

        if (Message_delivery == enum__Message_delivery.None)
        {
            panel.Children.Add(Noctalk_client__message__delivery_spinner());
            return panel;
        }

        string icon = Message_delivery switch
        {
            enum__Message_delivery.Delivered_to_server => "✓",
            enum__Message_delivery.Delivered_to_peer => "✓✓",
            enum__Message_delivery.Read_by_peer => "✓✓",
            enum__Message_delivery.Failed_to_send => "⚠",
            _ => string.Empty
        };

        Brush foreground = Message_delivery switch
        {
            enum__Message_delivery.Read_by_peer => Accent_blue,
            enum__Message_delivery.Failed_to_send => Text_danger,
            _ => Text_muted
        };

        panel.Children.Add(new TextBlock
        {
            Text = icon,
            FontSize = 12,
            Foreground = foreground,
            VerticalAlignment = VerticalAlignment.Center
        });

        return panel;
    }

    private static FrameworkElement Noctalk_client__message__delivery_spinner()
    {
        Ellipse spinner = new()
        {
            Width = 13,
            Height = 13,
            StrokeThickness = 2,
            Stroke = Text_muted,
            Fill = Brushes.Transparent,
            StrokeDashArray = [2, 2],
            HorizontalAlignment = HorizontalAlignment.Right,
            RenderTransformOrigin = new Point(0.5, 0.5)
        };

        RotateTransform rotation = new();
        spinner.RenderTransform = rotation;
        DoubleAnimation animation = new(0, 360, TimeSpan.FromMilliseconds(850))
        {
            RepeatBehavior = RepeatBehavior.Forever
        };
        rotation.BeginAnimation(RotateTransform.AngleProperty, animation);

        return spinner;
    }

    private void Noctalk_client__log__append(string The_line)
    {
        lock (queuedLogEntriesLock)
        {
            queuedLogEntries.Add(Noctalk.Shared.Logging.Noctalk_log.Log(The_line));
        }
    }

    private void Log_view_timer__tick(object? sender, EventArgs e)
    {
        Noctalk_client__log__drain_to_textbox();
    }

    private void Noctalk_client__log__drain_to_textbox()
    {
        lock (queuedLogEntriesLock)
        {
            if (queuedLogEntries.Count == 0)
            {
                return;
            }

            double scrollableHeight = Math.Max(0, Log_textbox.ExtentHeight - Log_textbox.ViewportHeight);
            bool wasNearBottom = scrollableHeight - Log_textbox.VerticalOffset <= Client_log_policy.Auto_scroll_bottom_threshold;
            double previousVerticalOffset = Log_textbox.VerticalOffset;

            foreach (string logEntry in queuedLogEntries)
            {
                Log_textbox.AppendText(logEntry + Environment.NewLine);
            }

            queuedLogEntries.Clear();
            Noctalk_client__log__recycle_text_if_needed();

            if (wasNearBottom)
            {
                Log_textbox.ScrollToEnd();
            }
            else
            {
                Log_textbox.ScrollToVerticalOffset(previousVerticalOffset);
            }
        }
    }

    private void Noctalk_client__log__recycle_text_if_needed()
    {
        if (Log_textbox.Text.Length <= Client_log_policy.Maximum_characters_before_recycle)
        {
            return;
        }

        int keepStartIndex = Math.Max(0, Log_textbox.Text.Length - Client_log_policy.Characters_after_recycle);
        int newlineIndex = Log_textbox.Text.IndexOf(Environment.NewLine, keepStartIndex, StringComparison.Ordinal);
        if (newlineIndex >= 0)
        {
            keepStartIndex = newlineIndex + Environment.NewLine.Length;
        }

        Log_textbox.Text = Log_textbox.Text[keepStartIndex..];
    }

    private void Noctalk_client__ui__set_online_state(bool Is_online)
    {
        isClientOnline = Is_online;
        Noctalk_client__ui__update_title();
    }

    private void Noctalk_client__ui__update_title()
    {
        if (isShowingAdmin)
        {
            Title = "Noctalk - Admin";
            return;
        }

        if (isShowingLog)
        {
            Title = "Noctalk - Log";
            return;
        }

        if (currentConversationConfig is not null)
        {
            Title = $"Noctalk - {currentConversationConfig.Peer__nickname}";
            return;
        }

        Title = $"Noctalk - Client - {(isClientOnline ? "Online" : "Offline")}";
    }

    private void Noctalk_client__ui__apply_view_state()
    {
        bool isShowingChat = !isShowingLog && !isShowingAdmin;
        Message_scroll.Visibility = isShowingChat ? Visibility.Visible : Visibility.Collapsed;
        Log_textbox.Visibility = isShowingLog ? Visibility.Visible : Visibility.Collapsed;
        Admin_content_host.Visibility = isShowingAdmin ? Visibility.Visible : Visibility.Collapsed;
        Message_input_panel.Visibility = isShowingChat ? Visibility.Visible : Visibility.Collapsed;
        Log_action_panel.Visibility = isShowingLog ? Visibility.Visible : Visibility.Collapsed;
        Admin_action_panel.Visibility = Visibility.Collapsed;
        Middle_content_grid.Margin = isShowingLog ? new Thickness(0) : new Thickness(14, 0, 14, 0);
        Middle_bottom_row.Height = isShowingLog || isShowingChat ? GridLength.Auto : new GridLength(0);
        Middle_header_label.Text = isShowingAdmin ? "Admin" : isShowingLog ? "Log" : currentConversationConfig?.Peer__nickname ?? string.Empty;
        if (isShowingLog)
        {
            logViewTimer.Start();
        }
        else
        {
            logViewTimer.Stop();
        }

        Noctalk_client__ui__update_chat_boundary_line();
        Noctalk_client__ui__update_title();
    }

    private void Noctalk_client__ui__update_chat_boundary_line()
    {
        bool shouldShowBoundary = !isShowingLog && Middle_root_grid.ActualWidth > 1500;
        Chat_max_width_boundary_line.Visibility = shouldShowBoundary ? Visibility.Visible : Visibility.Collapsed;
    }

    private void Noctalk_client__message_input__focus()
    {
        Dispatcher.BeginInvoke(() =>
        {
            if (!isShowingLog && !isShowingAdmin)
            {
                Send_message_area.Focus_input();
            }
        }, DispatcherPriority.Input);
    }

    private static SolidColorBrush UI__brush(byte Red, byte Green, byte Blue)
    {
        SolidColorBrush brush = new(Color.FromRgb(Red, Green, Blue));
        brush.Freeze();
        return brush;
    }

    private static SolidColorBrush UI__brush(byte Alpha, byte Red, byte Green, byte Blue)
    {
        SolidColorBrush brush = new(Color.FromArgb(Alpha, Red, Green, Blue));
        brush.Freeze();
        return brush;
    }
}
