using System.IO;
using System.Text.Json;
using System.Windows;

namespace DragonISO.App.Services;

/// <summary>
/// Saves / restores the main window's size, position, and state across launches.
/// Stored as JSON at <c>%LOCALAPPDATA%\Dragon-ISO\window.json</c>. Multi-monitor
/// friendly: a saved position that no longer falls inside any working area is
/// rejected on restore so the window doesn't disappear off-screen when a monitor
/// has been disconnected.
/// </summary>
public static class WindowStateStore
{
    /// <summary>
    /// Test-only seam â€” when set, overrides the default
    /// %LOCALAPPDATA%\Dragon-ISO\window.json path. Lets tests verify
    /// the serialization round-trip without polluting the dev's
    /// real placement state.
    /// </summary>
    internal static string? PathOverride { get; set; }

    private static string Path => PathOverride ??
        System.IO.Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
            "Dragon-ISO",
            "window.json");

    public sealed record Snapshot(
        double Left,
        double Top,
        double Width,
        double Height,
        WindowState State);

    /// <summary>Save the current window placement.</summary>
    public static void Save(Window window)
    {
        try
        {
            var snap = new Snapshot(
                Left: window.Left,
                Top: window.Top,
                Width: window.ActualWidth,
                Height: window.ActualHeight,
                State: window.WindowState == WindowState.Minimized ? WindowState.Normal : window.WindowState);
            var dir = System.IO.Path.GetDirectoryName(Path);
            if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir);
            File.WriteAllText(Path, JsonSerializer.Serialize(snap, new JsonSerializerOptions { WriteIndented = true }));
        }
        catch
        {
            // Best-effort persistence; never crash on shutdown for a UI nicety.
        }
    }

    /// <summary>
    /// Apply a previously-saved placement. Clamps onto a visible work area so a
    /// monitor change doesn't strand the window off-screen. Returns true if a
    /// valid snapshot was applied; false if no file existed or the snapshot was
    /// rejected for being entirely outside any visible work area.
    /// </summary>
    public static bool TryApply(Window window)
    {
        try
        {
            if (!File.Exists(Path)) return false;
            var json = File.ReadAllText(Path);
            var snap = JsonSerializer.Deserialize<Snapshot>(json);
            if (snap is null) return false;

            // Sanity-check sizes (don't restore a 0Ã—0 or absurdly large window).
            if (snap.Width < 320 || snap.Height < 240) return false;
            if (snap.Width > 16000 || snap.Height > 12000) return false;

            // Reject if entirely off-screen (any working area on any screen contains
            // a corner). System.Windows.Forms gives us per-monitor work areas here;
            // we deliberately stick with WPF's SystemParameters which only reports the
            // primary, so we use a generous on-screen check rather than refusing
            // multi-monitor positions.
            if (!IsAnyCornerOnScreen(snap)) return false;

            window.WindowStartupLocation = WindowStartupLocation.Manual;
            window.Left = snap.Left;
            window.Top = snap.Top;
            window.Width = snap.Width;
            window.Height = snap.Height;
            window.WindowState = snap.State;
            return true;
        }
        catch
        {
            return false;
        }
    }

    /// <summary>
    /// Approximate "is at least one corner of the saved rect within the virtual
    /// screen?" check. Uses SystemParameters.VirtualScreen* which spans every
    /// monitor.
    /// </summary>
    private static bool IsAnyCornerOnScreen(Snapshot snap)
    {
        var minX = SystemParameters.VirtualScreenLeft;
        var minY = SystemParameters.VirtualScreenTop;
        var maxX = minX + SystemParameters.VirtualScreenWidth;
        var maxY = minY + SystemParameters.VirtualScreenHeight;

        var corners = new[]
        {
            (snap.Left, snap.Top),
            (snap.Left + snap.Width, snap.Top),
            (snap.Left, snap.Top + snap.Height),
            (snap.Left + snap.Width, snap.Top + snap.Height),
        };
        foreach (var (x, y) in corners)
        {
            if (x >= minX && x <= maxX && y >= minY && y <= maxY)
                return true;
        }
        return false;
    }
}
