using System.Runtime.InteropServices;
using System.Windows;

namespace NexusShell.Services;

public sealed class MonitorScreenInfo
{
    public required string DeviceName { get; init; }
    public required bool IsPrimary { get; init; }
    public required Rect Bounds { get; init; }
}

public static class MonitorLayoutService
{
    public static IReadOnlyList<MonitorScreenInfo> GetScreens(double dpiScaleX, double dpiScaleY)
    {
        var monitors = new List<MonitorScreenInfo>();
        EnumDisplayMonitors(
            IntPtr.Zero,
            IntPtr.Zero,
            (IntPtr hMonitor, IntPtr _, ref RectStruct _, IntPtr __) =>
            {
                var info = new MonitorInfoEx { cbSize = Marshal.SizeOf<MonitorInfoEx>() };
                if (!GetMonitorInfo(hMonitor, ref info))
                {
                    return true;
                }

                var bounds = info.rcMonitor;
                monitors.Add(new MonitorScreenInfo
                {
                    DeviceName = info.szDevice.TrimEnd('\0'),
                    IsPrimary = (info.dwFlags & MonitorinfofPrimary) != 0,
                    Bounds = ToWpfBounds(bounds, dpiScaleX, dpiScaleY),
                });
                return true;
            },
            IntPtr.Zero);

        return monitors
            .OrderByDescending(static screen => screen.IsPrimary)
            .ToList();
    }

    public static MonitorScreenInfo? GetPrimaryScreen(double dpiScaleX, double dpiScaleY) =>
        GetScreens(dpiScaleX, dpiScaleY).FirstOrDefault(static screen => screen.IsPrimary);

    public static IReadOnlyList<MonitorScreenInfo> GetSecondaryScreens(double dpiScaleX, double dpiScaleY) =>
        GetScreens(dpiScaleX, dpiScaleY).Where(static screen => !screen.IsPrimary).ToList();

    private static Rect ToWpfBounds(RectStruct pixelBounds, double dpiScaleX, double dpiScaleY) =>
        new(
            pixelBounds.Left / dpiScaleX,
            pixelBounds.Top / dpiScaleY,
            pixelBounds.Width / dpiScaleX,
            pixelBounds.Height / dpiScaleY);

    private const uint MonitorinfofPrimary = 1;

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

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

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

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

        public int Width => Right - Left;
        public int Height => Bottom - Top;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    private struct MonitorInfoEx
    {
        public int cbSize;
        public RectStruct rcMonitor;
        public RectStruct rcWork;
        public uint dwFlags;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string szDevice;
    }
}
