using System.Runtime.InteropServices;
using Avalonia.Controls;

namespace DodoSSH.Client.App.Views;

/// <summary>
/// Moves the window's keyboard focus back out of the terminal's native child window.
/// </summary>
/// <remarks>
/// <para>
/// The two directions across this boundary are not symmetric, and only one of them needs anything
/// like this. <see cref="NativeWebView"/> is <c>Focusable</c> by default and its <c>OnGotFocus</c>
/// calls the adapter's <c>Focus()</c>, which on Windows is
/// <c>ICoreWebView2Controller::MoveFocus(PROGRAMMATIC)</c> — so an ordinary Avalonia
/// <c>Focus()</c> call on the control really does hand Win32 focus to WebView2. Measured: focus
/// lands on the <c>Chrome_WidgetWin_1</c> child and the page reports
/// <c>document.hasFocus() == true</c>. Nothing custom is needed to give the terminal the keyboard.
/// </para>
/// <para>
/// Coming back is where the package stops helping. <c>OnLostFocus</c> calls the adapter's
/// <c>ResignFocus()</c>, and on Windows that method is <b>empty</b> — so moving Avalonia's focus to
/// another control leaves Win32 focus on WebView2. Measured: after <c>textBox.Focus()</c> the
/// focused element is the <c>TextBox</c> while <c>GetFocus()</c> is still the WebView2 child and the
/// page still reports focus, which is a text box that shows a caret and silently receives nothing.
/// <c>Window.Activate()</c> and <c>Window.Focus()</c> were both measured and neither recovers it.
/// A real mouse click does, because Avalonia's window sets focus on pointer input — which is why the
/// symptom is invisible to anyone who clicks before typing.
/// </para>
/// <para>
/// So the hand-back has to be the Win32 call. This is safe to run at any time, including while the
/// application is in the background: both windows live on this thread's message queue, and
/// <c>SetFocus</c> confined to one queue changes which window receives keys without activating
/// anything or taking focus from another application.
/// </para>
/// </remarks>
internal static class NativeKeyboardFocus
{
    /// <summary>
    /// Returns keyboard focus to <paramref name="window"/>'s own window, so that Avalonia's focused
    /// element receives keystrokes again.
    /// </summary>
    /// <remarks>
    /// Focus an element afterwards. Collapsing the terminal leaves Avalonia with no focused element
    /// at all, so handing the keyboard back without also choosing a target means keystrokes reach
    /// the window and stop there.
    /// </remarks>
    internal static void ReturnTo(Window window)
    {
        // Only Windows hosts the WebView in a child window today. Elsewhere this is either
        // unnecessary or wrong, and doing nothing is the honest option until a spike says otherwise
        // — see docs/platform-flags.md on the unproven Linux backend.
        if (!OperatingSystem.IsWindows())
        {
            return;
        }

        if (window.TryGetPlatformHandle()?.Handle is { } handle && handle != IntPtr.Zero)
        {
            SetFocus(handle);
        }
    }

    /// <remarks>
    /// <c>DllImport</c> rather than the source-generated <c>LibraryImport</c>, which requires
    /// <c>AllowUnsafeBlocks</c> for the whole project. The signature is blittable, so there is no
    /// marshalling stub for the generator to improve on, and turning unsafe code on across a client
    /// that handles key material to save nothing would be a poor trade. Hence the SYSLIB1054
    /// suppression rather than a fix.
    /// </remarks>
#pragma warning disable SYSLIB1054
    [DllImport("user32.dll")]
    private static extern IntPtr SetFocus(IntPtr window);
#pragma warning restore SYSLIB1054
}
