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

namespace DodoSSH.Client.App.Views;

/// <summary>
/// Paints the frame Windows still draws around a <c>BorderOnly</c> window in the application's own
/// colour, so the top edge stops reading as a leftover system titlebar.
/// </summary>
/// <remarks>
/// <para>
/// <b>The symptom this exists for:</b> a pale strip across the very top of the window, a few pixels
/// tall and plainly not part of the application — most obvious on a machine with "show accent colour
/// on title bars and window borders" turned on, where it comes out blue against a near-black shell.
/// </para>
/// <para>
/// It is not <c>TitleBar.axaml</c> leaking and it is not a margin. It is DWM, and the reason it is
/// there is visible in Avalonia's own Win32 backend: <c>WindowImpl.UpdateWindowProperties</c> gives a
/// <see cref="WindowDecorations.BorderOnly"/> window <c>WS_BORDER | WS_THICKFRAME</c> and then calls
/// <c>DwmExtendFrameIntoClientArea</c> with one-pixel margins on all four sides. So the compositor
/// owns a hairline of every edge of this window, and it fills that hairline with the system's caption
/// and border colours — which are chosen by the user's personalisation settings and have no reason to
/// resemble <c>CanvasColor</c>. The window is the wrong place to look for the pixels; they were never
/// painted by anything in this tree.
/// </para>
/// <para>
/// The fix is to tell DWM what colour to use rather than to try to cover it. <c>DWMWA_BORDER_COLOR</c>
/// and <c>DWMWA_CAPTION_COLOR</c> arrived in Windows 11 21H2 and are exactly that; both are set to the
/// window's own background, so the hairline still exists — the resize grip is on it, and the drop
/// shadow hangs off it — and simply cannot be seen. Deliberately <em>not</em> <c>DWMWA_COLOR_NONE</c>,
/// which removes the border outright: on a dark desktop that leaves a near-black window with no edge
/// at all, which trades one visual defect for another.
/// </para>
/// <para>
/// Windows 10 gets the dark-mode attribute and nothing else, and that is the whole of what is
/// available there: the two colour attributes are unsupported, <c>DwmSetWindowAttribute</c> answers
/// <c>E_INVALIDARG</c>, and the calls do nothing. Hence the ignored return values — every attribute
/// here is an improvement where it lands and a no-op where it does not, so there is nothing for a
/// caller to handle and nothing worth logging on a path that runs once at startup.
/// </para>
/// </remarks>
internal static class NativeWindowFrame
{
    /// <summary>Windows 11 21H2 and later: the colour of the frame border.</summary>
    private const int BorderColorAttribute = 34;

    /// <summary>Windows 11 21H2 and later: the colour of the caption, including the extended frame.</summary>
    private const int CaptionColorAttribute = 35;

    /// <summary>
    /// Windows 10 1903 and later: draw the frame in the dark palette.
    /// </summary>
    /// <remarks>
    /// Redundant on Windows 11, where the two colour attributes above name the colours outright, and it
    /// is set anyway because it is the only one of the three that Windows 10 honours. The build before
    /// 1903 used attribute 19 for this; that is not chased here, because a border on an OS release that
    /// left support in 2020 is not worth a second interop call.
    /// </remarks>
    private const int DarkModeAttribute = 20;

    /// <summary>
    /// Matches <paramref name="window"/>'s system-drawn frame to the colour it paints itself.
    /// </summary>
    /// <remarks>
    /// Call once the window has a handle — <c>OnOpened</c> is the first such moment. Calling earlier
    /// finds no platform handle and silently does nothing, which is the defect this replaced: the strip
    /// is only visible once the window is on screen, so a call that ran too early looks like a fix that
    /// does not work rather than a fix that never ran.
    /// </remarks>
    internal static void MatchTo(Window window)
    {
        // Every attribute below is a DWM one, and DWM is Windows. Elsewhere the frame is drawn by the
        // platform's own compositor and there is nothing here to say to it.
        if (!OperatingSystem.IsWindows())
        {
            return;
        }

        if (window.TryGetPlatformHandle()?.Handle is not { } handle || handle == IntPtr.Zero)
        {
            return;
        }

        Set(handle, DarkModeAttribute, 1);

        // The window's own Background rather than a named resource, so the frame cannot drift from the
        // canvas when the palette moves. A brush that is not solid — a gradient, or nothing set at all —
        // has no single colour to match, and leaving the system's own is better than inventing one.
        if (window.Background is not ISolidColorBrush { Color: var canvas })
        {
            return;
        }

        var reference = ColorRef(canvas);
        Set(handle, BorderColorAttribute, reference);
        Set(handle, CaptionColorAttribute, reference);
    }

    /// <summary>
    /// Sets one integer-valued DWM attribute, and discards the answer.
    /// </summary>
    /// <remarks>
    /// The discard is the point of this method existing rather than being three call sites. Every
    /// attribute here is unsupported on some Windows this application runs on, and unsupported means
    /// <c>E_INVALIDARG</c> and no change — which is the intended outcome on that OS, not a failure, so
    /// there is nothing for the caller to do with the <c>HRESULT</c> and nothing worth logging once at
    /// startup. Written once, with the reasoning, rather than left implicit at each call.
    /// </remarks>
    private static void Set(IntPtr window, int attribute, int value) =>
        _ = DwmSetWindowAttribute(window, attribute, ref value, sizeof(int));

    /// <summary>
    /// Packs <paramref name="color"/> into a Win32 <c>COLORREF</c>.
    /// </summary>
    /// <remarks>
    /// <c>0x00BBGGRR</c> — blue in the high byte, not red, and the alpha byte must be zero. Getting the
    /// order wrong produces a plausible-looking wrong colour rather than an error, which is the kind of
    /// bug that survives a glance at the window.
    /// </remarks>
    private static int ColorRef(Color color) => color.R | (color.G << 8) | (color.B << 16);

    /// <remarks>
    /// <c>DllImport</c> rather than <c>LibraryImport</c>, for the reason
    /// <see cref="NativeKeyboardFocus"/> gives at its own P/Invoke: the generated form needs
    /// <c>AllowUnsafeBlocks</c> across a project that handles key material, and this signature is
    /// blittable, so there is no marshalling for it to improve.
    /// </remarks>
#pragma warning disable SYSLIB1054
    [DllImport("dwmapi.dll")]
    private static extern int DwmSetWindowAttribute(IntPtr window, int attribute, ref int value, int size);
#pragma warning restore SYSLIB1054
}
