using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows.Interop;

namespace ytLive.Services;

/// <summary>A physical display: its capture-key index, geometry, and primary flag.</summary>
public sealed record DisplayInfo(int Index, string Name, int Width, int Height, int X, int Y, bool IsPrimary)
{
    public string Label
        => IsPrimary ? $"{Name} — {Width}×{Height} (primary)" : $"{Name} — {Width}×{Height}";
}

/// <summary>
/// Win32 detection: <c>GetForegroundWindow</c> + <c>DwmGetWindowAttribute</c>
/// (DWMWA_EXTENDED_FRAME_BOUNDS) + <c>MonitorFromWindow</c> + <c>GetMonitorInfo</c>.
/// A foreground window whose extended frame bounds cover an entire monitor is
/// treated as a full-screen game/app on that monitor's index (monitor order =
/// <c>EnumDisplayMonitors</c> enumeration order, the same order the capture
/// factory uses to map index → HMONITOR). Windows of our own process are excluded.
/// DRM-protected content captures as black frames — a documented OS limit.
/// </summary>
public sealed class Win32FullScreenDetector : IFullScreenDetector
{
    private const int DwmwaExtendedFrameBounds = 9;
    private const uint MonitorDefaultToNearest = 2;
    private const uint MonitorInfoFPrimary = 0x00000001;

    public int? GetForegroundFullScreenMonitorIndex()
    {
        var hwnd = GetForegroundWindow();
        if (hwnd == IntPtr.Zero || IsOwnWindow(hwnd)) return null;
        if (!TryGetExtendedFrameBounds(hwnd, out var bounds)) return null;

        var monitor = MonitorFromWindow(hwnd, MonitorDefaultToNearest);
        if (monitor == IntPtr.Zero || !TryGetMonitorInfo(monitor, out var info)) return null;

        var r = info.RcMonitor;
        var coversMonitor = bounds.Left <= r.Left && bounds.Top <= r.Top &&
                            bounds.Right >= r.Right && bounds.Bottom >= r.Bottom;
        if (!coversMonitor) return null;

        return MonitorIndex(monitor);
    }

    private static bool IsOwnWindow(IntPtr hwnd)
    {
        _ = GetWindowThreadProcessId(hwnd, out var pid);
        return pid == Environment.ProcessId;
    }

    private static bool TryGetExtendedFrameBounds(IntPtr hwnd, out Win32Rect bounds)
    {
        bounds = default;
        return DwmGetWindowAttribute(hwnd, DwmwaExtendedFrameBounds, ref bounds, Marshal.SizeOf<Win32Rect>()) == 0;
    }

    private static bool TryGetMonitorInfo(IntPtr monitor, out MonitorInfo info)
    {
        info = new MonitorInfo { CbSize = Marshal.SizeOf<MonitorInfo>() };
        return GetMonitorInfo(monitor, ref info);
    }

    private static int? MonitorIndex(IntPtr monitor)
    {
        var handles = EnumerateMonitors();
        var index = handles.IndexOf(monitor);
        return index >= 0 ? index : null;
    }

    public int PrimaryMonitorIndex()
        => GetDisplays().FirstOrDefault(d => d.IsPrimary)?.Index ?? 0;

    public IReadOnlyList<DisplayInfo> GetDisplays()
    {
        var result = new List<DisplayInfo>();
        var handles = EnumerateMonitors();
        for (var i = 0; i < handles.Count; i++)
        {
            if (!TryGetMonitorInfo(handles[i], out var info)) continue;
            var name = GetDisplayName(handles[i]);
            result.Add(new DisplayInfo(
                i,
                name,
                info.RcMonitor.Right - info.RcMonitor.Left,
                info.RcMonitor.Bottom - info.RcMonitor.Top,
                info.RcMonitor.Left,
                info.RcMonitor.Top,
                (info.DwFlags & MonitorInfoFPrimary) != 0));
        }
        return result;
    }

    private static string GetDisplayName(IntPtr monitor)
    {
        var info = new MonitorInfoEx { CbSize = Marshal.SizeOf<MonitorInfoEx>() };
        if (!GetMonitorInfo(monitor, ref info)) return $"Display {monitor}";
        var deviceName = info.SzDevice.TrimEnd('\0');
        var dev = new DisplayDevice { Cb = Marshal.SizeOf<DisplayDevice>() };
        if (!EnumDisplayDevices(deviceName, 0, ref dev, 0)) return deviceName;
        var friendly = dev.DeviceString.TrimEnd('\0');
        return string.IsNullOrWhiteSpace(friendly) ? deviceName : friendly;
    }

    public static IntPtr GetMonitorHandle(int index)
    {
        var handles = EnumerateMonitors();
        return index >= 0 && index < handles.Count ? handles[index] : IntPtr.Zero;
    }

    private static List<IntPtr> EnumerateMonitors()
    {
        var handles = new List<IntPtr>();
        EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero,
            (IntPtr hMonitor, IntPtr hdcMonitor, ref Win32Rect lprcMonitor, IntPtr dwData) =>
            {
                handles.Add(hMonitor);
                return true;
            }, IntPtr.Zero);
        return handles;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct Win32Rect
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct MonitorInfo
    {
        public int CbSize;
        public Win32Rect RcMonitor;
        public Win32Rect RcWork;
        public uint DwFlags;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct MonitorInfoEx
    {
        public int CbSize;
        public Win32Rect RcMonitor;
        public Win32Rect RcWork;
        public uint DwFlags;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string SzDevice;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    private struct DisplayDevice
    {
        public int Cb;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string DeviceName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceString;
        public uint StateFlags;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceId;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceKey;
    }

    [DllImport("user32.dll")]
    private static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll")]
    private static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags);

    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MonitorInfo lpmi);

    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MonitorInfoEx lpmi);

    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    private static extern bool EnumDisplayDevices(string lpDevice, uint iDevNum, ref DisplayDevice lpDisplayDevice, uint dwFlags);

    [DllImport("user32.dll")]
    private static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip,
        MonitorEnumProc lpfnEnum, IntPtr dwData);

    [DllImport("user32.dll")]
    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

    [DllImport("dwmapi.dll")]
    private static extern int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, ref Win32Rect pvAttribute, int cbAttribute);

    private delegate bool MonitorEnumProc(IntPtr hMonitor, IntPtr hdcMonitor, ref Win32Rect lprcMonitor, IntPtr dwData);
}
