using System.Runtime.InteropServices;

namespace DragonISO.App.Services;

/// <summary>
/// Phase E.4 â€” Embedded Teams via SetParent.
///
/// Reparents Teams' main top-level window into a Dragon-ISO-owned host
/// (typically a Border element's HWND). Strips the captured window's
/// caption + thick frame so it integrates flush with the host, and
/// remembers enough about the original to restore it cleanly later.
///
/// The Win32 behavior is well understood for classic Win32 apps, but
/// modern Teams runs WebView2 in its main window; WebView2's renderer is
/// sensitive to parent changes and may flash white frames during
/// reparent, drop input focus, or refuse to redraw until forced. We mark
/// the feature experimental and ensure the restore path always runs (the
/// caller wraps Embed in a finally block) so operators can fall back to
/// auto-hide mode if embedding misbehaves on their specific Teams build.
///
/// Lives in its own static class â€” separated from <see cref="TeamsLauncher"/>
/// because the embedding lifecycle (reparent â†’ resize â†’ restore) is its
/// own thing, and the Win32 surface it requires (SetParent / window-style
/// muck / SetWindowPos / MoveWindow) isn't reused by the launch / hide /
/// in-call control paths.
/// </summary>
public static class TeamsEmbedHost
{
    [DllImport("user32.dll", SetLastError = true)]
    private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern int GetWindowLongPtr(IntPtr hWnd, int nIndex);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern int SetWindowLongPtr(IntPtr hWnd, int nIndex, int dwNewLong);

    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, [MarshalAs(UnmanagedType.Bool)] bool bRepaint);

    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

    [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern int GetWindowTextLengthW(IntPtr hWnd);

    private const int GWL_STYLE = -16;
    private const long WS_CHILD = 0x40000000;
    private const long WS_POPUP = unchecked((long)0x80000000);
    private const long WS_CAPTION = 0x00C00000;
    private const long WS_THICKFRAME = 0x00040000;
    private const long WS_BORDER = 0x00800000;
    private const long WS_DLGFRAME = 0x00400000;
    private const uint SWP_FRAMECHANGED = 0x0020;
    private const uint SWP_NOMOVE = 0x0002;
    private const uint SWP_NOSIZE = 0x0001;
    private const uint SWP_NOZORDER = 0x0004;
    private const uint SWP_NOACTIVATE = 0x0010;

    /// <summary>
    /// Captures the original parent + window style so embedding can be
    /// reversed cleanly. Tracked per-HWND so multiple consecutive
    /// embed / unembed cycles don't lose the original chrome.
    /// </summary>
    private static (IntPtr OriginalParent, int OriginalStyle)? _embedSavedState;
    private static IntPtr _embeddedHwnd = IntPtr.Zero;

    /// <summary>True when a Teams window is currently parented inside a Dragon-ISO host.</summary>
    public static bool IsEmbedded => _embeddedHwnd != IntPtr.Zero;

    /// <summary>
    /// Reparents Teams' most-recently-used top-level window into
    /// <paramref name="hostHwnd"/>. Strips Teams' caption + thick frame
    /// so it integrates flush with the host. Returns true on success,
    /// false if no Teams window could be found.
    ///
    /// The host HWND is typically obtained via:
    ///   var src = (System.Windows.Interop.HwndSource)
    ///                 PresentationSource.FromVisual(MyHostBorder);
    ///   src.Handle  // â†’ IntPtr suitable for hostHwnd
    /// </summary>
    public static bool EmbedTeamsInto(IntPtr hostHwnd, int width, int height)
    {
        if (hostHwnd == IntPtr.Zero) return false;
        var teamsWindows = TeamsLauncher.EnumerateTopLevelTeamsWindows();
        if (teamsWindows.Count == 0) return false;

        // Pick the longest-title window as the "main" one â€” same
        // heuristic GetActiveWindowTitle uses; matches the call /
        // meeting window.
        IntPtr best = IntPtr.Zero;
        int bestLen = -1;
        foreach (var w in teamsWindows)
        {
            var len = GetWindowTextLengthW(w);
            if (len > bestLen) { bestLen = len; best = w; }
        }
        if (best == IntPtr.Zero) return false;

        // Already embedded? Unembed first to clean state.
        if (_embeddedHwnd != IntPtr.Zero) RestoreEmbed();

        // Save original style + parent so we can fully reverse later.
        var originalStyle = GetWindowLongPtr(best, GWL_STYLE);
        var originalParent = SetParent(best, hostHwnd); // returns old parent

        _embedSavedState = (originalParent, originalStyle);
        _embeddedHwnd = best;

        // Strip top-level decorations + add WS_CHILD so the OS treats
        // it as a child window of the host.
        var newStyle = originalStyle;
        unchecked
        {
            newStyle &= ~(int)WS_CAPTION;
            newStyle &= ~(int)WS_THICKFRAME;
            newStyle &= ~(int)WS_BORDER;
            newStyle &= ~(int)WS_DLGFRAME;
            newStyle &= ~(int)WS_POPUP;
            newStyle |= (int)WS_CHILD;
        }
        SetWindowLongPtr(best, GWL_STYLE, newStyle);

        // Force a non-client recalculation so the style change takes
        // effect.
        SetWindowPos(best, IntPtr.Zero, 0, 0, 0, 0,
            SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);

        // Place at top-left of host, full host size.
        MoveWindow(best, 0, 0, width, height, true);
        return true;
    }

    /// <summary>
    /// Resize the currently-embedded Teams window to <paramref name="width"/>
    /// Ã— <paramref name="height"/>. Called when the host element resizes
    /// (window resize, layout change, etc.). No-op if nothing is embedded.
    /// </summary>
    public static void ResizeEmbedded(int width, int height)
    {
        if (_embeddedHwnd == IntPtr.Zero || width <= 0 || height <= 0) return;
        MoveWindow(_embeddedHwnd, 0, 0, width, height, true);
    }

    /// <summary>
    /// Reverse an active embed: SetParent back to desktop + restore the
    /// original window style so Teams looks/behaves like a normal
    /// top-level window again. Safe to call when nothing is embedded â€”
    /// no-op.
    /// </summary>
    public static void RestoreEmbed()
    {
        if (_embeddedHwnd == IntPtr.Zero || _embedSavedState is null) return;
        var (origParent, origStyle) = _embedSavedState.Value;
        try
        {
            // Restore original style FIRST so when we reparent the
            // window's top-level decorations come back correctly.
            SetWindowLongPtr(_embeddedHwnd, GWL_STYLE, origStyle);
            // SetParent(hwnd, Zero) returns to desktop. We could pass
            // origParent verbatim but for Teams that's always the
            // desktop anyway, and IntPtr.Zero is documented as
            // "reparent to desktop".
            SetParent(_embeddedHwnd, IntPtr.Zero);
            SetWindowPos(_embeddedHwnd, IntPtr.Zero, 0, 0, 0, 0,
                SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
        }
        catch { /* defensive â€” restore must never throw */ }
        finally
        {
            _embedSavedState = null;
            _embeddedHwnd = IntPtr.Zero;
        }
    }
}
