using System.Runtime.InteropServices;

namespace NexusShell.Services;

public sealed class DisplayMonitorInfo
{
    public required string DeviceName { get; init; }
    public required string FriendlyName { get; init; }
    public int CurrentWidth { get; init; }
    public int CurrentHeight { get; init; }
    public int RefreshRate { get; init; }
    public bool IsPrimary { get; init; }
    public IReadOnlyList<DisplayModeInfo> Modes { get; init; } = [];
}

public sealed class DisplayModeInfo
{
    public required string Label { get; init; }
    public int Width { get; init; }
    public int Height { get; init; }
    public int RefreshRate { get; init; }

    public override string ToString() => Label;
}

public sealed class DisplaySettingsService
{
    private const int EnumCurrentSettings = -1;
    private const int DmPelsWidth = 0x80000;
    private const int DmPelsHeight = 0x100000;
    private const int DmDisplayFrequency = 0x400000;
    private const int CdsUpdatedRegistry = 0x01;
    private const int DispChangeSuccessful = 0;

    public IReadOnlyList<DisplayMonitorInfo> GetMonitors()
    {
        var monitors = new List<DisplayMonitorInfo>();
        var device = new DisplayDevice { cb = Marshal.SizeOf<DisplayDevice>() };
        for (uint deviceIndex = 0; EnumDisplayDevices(null, deviceIndex, ref device, 0); deviceIndex++)
        {
            if ((device.StateFlags & DisplayDeviceActive) == 0 ||
                (device.StateFlags & DisplayDeviceMirror) != 0)
            {
                continue;
            }

            var current = CreateDevMode();
            if (!EnumDisplaySettings(device.DeviceName, EnumCurrentSettings, ref current))
            {
                continue;
            }

            monitors.Add(new DisplayMonitorInfo
            {
                DeviceName = device.DeviceName,
                FriendlyName = string.IsNullOrWhiteSpace(device.DeviceString)
                    ? device.DeviceName
                    : device.DeviceString,
                CurrentWidth = current.dmPelsWidth,
                CurrentHeight = current.dmPelsHeight,
                RefreshRate = current.dmDisplayFrequency,
                IsPrimary = (device.StateFlags & DisplayDevicePrimary) != 0,
                Modes = GetSupportedModes(device.DeviceName, current),
            });
        }

        return monitors;
    }

    public bool TryApplyMode(string deviceName, int width, int height, int refreshRate, out string? error)
    {
        error = null;
        var devMode = CreateDevMode();
        devMode.dmSize = (short)Marshal.SizeOf<DevMode>();
        devMode.dmFields = DmPelsWidth | DmPelsHeight | DmDisplayFrequency;
        devMode.dmPelsWidth = width;
        devMode.dmPelsHeight = height;
        devMode.dmDisplayFrequency = refreshRate;

        var result = ChangeDisplaySettingsEx(deviceName, ref devMode, IntPtr.Zero, CdsUpdatedRegistry, IntPtr.Zero);
        if (result == DispChangeSuccessful)
        {
            return true;
        }

        error = DescribeDisplayChangeResult(result);
        return false;
    }

    private static List<DisplayModeInfo> GetSupportedModes(string deviceName, DevMode current)
    {
        var modes = new Dictionary<string, DisplayModeInfo>();
        var mode = CreateDevMode();
        for (var index = 0; EnumDisplaySettings(deviceName, index, ref mode); index++)
        {
            if (mode.dmPelsWidth <= 0 || mode.dmPelsHeight <= 0)
            {
                continue;
            }

            var refresh = mode.dmDisplayFrequency > 0 ? mode.dmDisplayFrequency : 60;
            var key = $"{mode.dmPelsWidth}x{mode.dmPelsHeight}@{refresh}";
            if (modes.ContainsKey(key))
            {
                continue;
            }

            modes[key] = new DisplayModeInfo
            {
                Label = $"{mode.dmPelsWidth} × {mode.dmPelsHeight} @ {refresh} Hz",
                Width = mode.dmPelsWidth,
                Height = mode.dmPelsHeight,
                RefreshRate = refresh,
            };
        }

        var list = modes.Values
            .OrderByDescending(static m => m.Width * m.Height)
            .ThenByDescending(static m => m.RefreshRate)
            .ToList();

        var currentKey = $"{current.dmPelsWidth}x{current.dmPelsHeight}@{Math.Max(current.dmDisplayFrequency, 60)}";
        if (list.All(m => $"{m.Width}x{m.Height}@{m.RefreshRate}" != currentKey))
        {
            list.Insert(0, new DisplayModeInfo
            {
                Label = $"{current.dmPelsWidth} × {current.dmPelsHeight} @ {Math.Max(current.dmDisplayFrequency, 60)} Hz (nuværende)",
                Width = current.dmPelsWidth,
                Height = current.dmPelsHeight,
                RefreshRate = Math.Max(current.dmDisplayFrequency, 60),
            });
        }

        return list;
    }

    private static string DescribeDisplayChangeResult(int result) => result switch
    {
        -1 => "Skærmindstillingen understøttes ikke.",
        1 => "Genstart Nexus for at fuldføre skærmændringen.",
        _ => $"Windows afviste skærmændringen (kode {result}).",
    };

    private static DevMode CreateDevMode() => new() { dmSize = (short)Marshal.SizeOf<DevMode>() };

    private const uint DisplayDeviceActive = 0x00000001;
    private const uint DisplayDeviceMirror = 0x00000002;
    private const uint DisplayDevicePrimary = 0x00000004;

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

    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    private static extern bool EnumDisplaySettings(string lpszDeviceName, int iModeNum, ref DevMode lpDevMode);

    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    private static extern int ChangeDisplaySettingsEx(string lpszDeviceName, ref DevMode lpDevMode, IntPtr hwnd, int dwflags, IntPtr lParam);

    [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;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    private struct DevMode
    {
        private const int CchDeviceName = 32;
        private const int CchFormName = 32;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CchDeviceName)]
        public string dmDeviceName;
        public short dmSpecVersion;
        public short dmDriverVersion;
        public short dmSize;
        public short dmDriverExtra;
        public int dmFields;
        public int dmPositionX;
        public int dmPositionY;
        public int dmDisplayOrientation;
        public int dmDisplayFixedOutput;
        public short dmColor;
        public short dmDuplex;
        public short dmYResolution;
        public short dmTTOption;
        public short dmCollate;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CchFormName)]
        public string dmFormName;
        public short dmLogPixels;
        public int dmBitsPerPel;
        public int dmPelsWidth;
        public int dmPelsHeight;
        public int dmDisplayFlags;
        public int dmDisplayFrequency;
        public int dmICMMethod;
        public int dmICMIntent;
        public int dmMediaType;
        public int dmDitherType;
        public int dmReserved1;
        public int dmReserved2;
        public int dmPanningWidth;
        public int dmPanningHeight;
    }
}
