using System.Runtime.InteropServices;
using System.Text;

namespace RioJoy.Tray;

/// <summary>
/// Sets the Windows desktop wallpaper to a generated cockpit overlay
/// (Phase 7 — the modern replacement for manually setting the GIMP-exported
/// background). Wraps <c>SystemParametersInfo(SPI_SETDESKWALLPAPER)</c>.
///
/// <para>Windows requires a <strong>BMP</strong> file path for reliable results on
/// all versions; PNGs are accepted on modern Windows but not guaranteed, so callers
/// should hand this a path the overlay renderer wrote. Applying a wallpaper changes
/// a user-visible system setting, so it is only invoked on an explicit profile
/// activation, never speculatively.</para>
/// </summary>
public static class WallpaperApplier
{
    private const int SPI_SETDESKWALLPAPER = 0x0014;
    private const int SPI_GETDESKWALLPAPER = 0x0073;
    private const int SPIF_UPDATEINIFILE = 0x01; // persist across logon
    private const int SPIF_SENDCHANGE = 0x02;    // broadcast WM_SETTINGCHANGE
    private const int MaxPath = 260;

    /// <summary>
    /// Apply <paramref name="imagePath"/> as the desktop wallpaper. Returns true on
    /// success. Throws if the file does not exist.
    /// </summary>
    public static bool Apply(string imagePath)
    {
        if (string.IsNullOrWhiteSpace(imagePath)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(imagePath));
        string full = Path.GetFullPath(imagePath);
        if (!File.Exists(full))
            throw new FileNotFoundException("Wallpaper image not found.", full);

#if NET40
        // Windows XP's SPI_SETDESKWALLPAPER accepts only BMP. Wallpapers are
        // authored as PNG on a modern machine, so convert beside the source on
        // apply (idempotent: reuses an up-to-date .bmp).
        if (!full.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase))
        {
            string bmp = Path.ChangeExtension(full, ".bmp");
            if (!File.Exists(bmp) || File.GetLastWriteTimeUtc(bmp) < File.GetLastWriteTimeUtc(full))
            {
                using var image = System.Drawing.Image.FromFile(full);
                image.Save(bmp, System.Drawing.Imaging.ImageFormat.Bmp);
            }
            full = bmp;
        }
#endif

        return SystemParametersInfo(
            SPI_SETDESKWALLPAPER, 0, full, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
    }

    /// <summary>
    /// Read the desktop wallpaper Windows currently has set (empty string when none
    /// is set — a solid-color desktop). Capture this before <see cref="Apply"/>
    /// overrides it, so <see cref="Restore"/> can put it back when going dormant.
    /// </summary>
    public static string GetCurrent()
    {
        var buffer = new StringBuilder(MaxPath);
        bool ok = SystemParametersInfo(SPI_GETDESKWALLPAPER, buffer.Capacity, buffer, 0);
        return ok ? buffer.ToString() : string.Empty;
    }

    /// <summary>
    /// Restore a wallpaper previously read by <see cref="GetCurrent"/>. Unlike
    /// <see cref="Apply"/> this does not require the file to exist and accepts an
    /// empty path (which clears the wallpaper to the solid desktop color) — the
    /// value came straight from Windows, so it is passed back verbatim. Returns the
    /// API result; callers treat it as best-effort.
    /// </summary>
    public static bool Restore(string? previous)
    {
        return SystemParametersInfo(
            SPI_SETDESKWALLPAPER, 0, previous ?? string.Empty, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
    }

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SystemParametersInfo(int uAction, int uParam, StringBuilder lpvParam, int fuWinIni);
}
