using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Threading;

enum Theme { Dark, SE98 }
static class ThemeManager { public static Theme Current = Theme.SE98; }

// The script inserts the live WindowChrome annotation methods verbatim here.
sealed class AnnotationFixture : DispatcherObject
{
    private ScrollViewer PagePreviewPanel = new();
    private Border? _drawSettingsBar;
    private Border? _textSettingsBar = null;
    private double? _annotBarCenterFrac = null;
    private bool _annotBarAnchorRight = true;
    private double? _annotBarGap = null;
    public WindowState WindowState { get; set; }
    public void Start(Border bar, Grid area)
    {
        _drawSettingsBar = bar;
        PagePreviewPanel = (ScrollViewer)area.Children[0];
        bar.SizeChanged -= AnnotBarSizeChanged;
        bar.SizeChanged += AnnotBarSizeChanged;
        PositionAnnotationBar(bar, area);
    }
    // LIVE_ANNOTATION_METHODS
}

static class RegressionProgram
{
    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool IsWindowVisible(IntPtr window);

    static readonly List<object> Results = new();
    static void Require(bool value, string message)
    {
        if (!value) throw new InvalidOperationException(message);
    }

    // A Send-priority timer and a callback count bound even the old infinite Loaded loop.
    static int Pump(int maxLoaded = 32)
    {
        var dispatcher = Dispatcher.CurrentDispatcher;
        var frame = new DispatcherFrame();
        int loaded = 0;
        bool input = false, background = false, timedOut = false;
        var pending = new List<DispatcherOperation>();
        DispatcherHookEventHandler posted = (_, e) => pending.Add(e.Operation);
        DispatcherHookEventHandler started = (_, e) =>
        {
            if (e.Operation.Priority == DispatcherPriority.Loaded && ++loaded >= maxLoaded)
                frame.Continue = false;
        };
        dispatcher.Hooks.OperationPosted += posted;
        dispatcher.Hooks.OperationStarted += started;
        dispatcher.BeginInvoke(DispatcherPriority.Input, (Action)(() => input = true));
        dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)(() => { background = true; frame.Continue = false; }));
        using var timer = new Timer(_ => dispatcher.BeginInvoke(DispatcherPriority.Send,
            (Action)(() => { timedOut = true; frame.Continue = false; })), null, 1500, Timeout.Infinite);
        try { Dispatcher.PushFrame(frame); }
        finally
        {
            dispatcher.Hooks.OperationPosted -= posted;
            dispatcher.Hooks.OperationStarted -= started;
            foreach (var operation in pending)
                if (operation.Status == DispatcherOperationStatus.Pending) operation.Abort();
        }
        Require(!timedOut && input && background, $"Dispatcher starved: loaded={loaded}, input={input}, background={background}");
        return loaded;
    }

    static (Grid Area, ScrollViewer Scroller, Border Bar) Elements(double height = 0)
    {
        var area = new Grid { Width = 320, Height = 180 };
        var scroller = new ScrollViewer();
        var bar = new Border { Height = height, Width = 80, VerticalAlignment = VerticalAlignment.Top };
        area.Children.Add(scroller);
        area.Children.Add(bar);
        return (area, scroller, bar);
    }
    static void Layout(Grid area)
    {
        area.Measure(new Size(320, 180));
        area.Arrange(new Rect(0, 0, 320, 180));
        area.UpdateLayout();
    }
    static void Record(string name, Action body)
    {
        body();
        Results.Add(new { name, pass = true });
        Console.WriteLine("PASS " + name);
    }

    [STAThread]
    static int Main(string[] args)
    {
        try
        {
            // No Application, MainWindow, documents, settings or visible HWND. This source owns
            // one hidden popup solely to obtain real WPF Loaded/IsVisible lifecycle events.
            using var host = new HwndSource(new HwndSourceParameters("Annotation layout regression")
            {
                WindowStyle = unchecked((int)0x80000000), Width = 320, Height = 180
            });
            Require(Application.Current is null && !IsWindowVisible(host.Handle), "Fixture isolation failed");
            var fixture = new AnnotationFixture();
            var (area, scroller, bar) = Elements();
            host.RootVisual = area;
            Pump();
            Require(bar.IsLoaded && bar.IsVisible && bar.ActualHeight == 0, "Zero-height loaded fixture unavailable");

            Record("zero height is bounded and Input/Background run", () =>
            {
                fixture.Start(bar, area);
                Require(Pump() <= 2, "Unbounded retry");
            });
            Record("repeated placement requests coalesce", () =>
            {
                for (int i = 0; i < 20; i++) fixture.Start(bar, area);
                Require(Pump() <= 2, "Retries did not coalesce");
            });
            Record("measurement resumes and tracks inset", () =>
            {
                bar.Height = 24; Layout(area); Pump();
                Require(scroller.Margin.Top == 24, "Initial measurement did not set inset");
                bar.Height = 48; Layout(area); Pump();
                Require(scroller.Margin.Top == 48, "Resizing did not update inset");
                Require(bar.HorizontalAlignment == HorizontalAlignment.Stretch, "98SE band lost stretch");
            });
            Record("minimize cancels pending and restore resumes", () =>
            {
                bar.Height = 0; Layout(area); fixture.Start(bar, area);
                fixture.WindowState = WindowState.Minimized; fixture.RepositionAnnotationBars();
                Require(Pump() == 0, "Minimize left a retry pending");
                fixture.WindowState = WindowState.Normal; fixture.RepositionAnnotationBars(); Pump();
                bar.Height = 30; Layout(area); Pump();
                Require(scroller.Margin.Top == 30, "Restore lost inset");
            });
            Record("hidden parent cancels and visibility resumes", () =>
            {
                bar.Height = 0; Layout(area); fixture.Start(bar, area);
                area.Visibility = Visibility.Collapsed;
                Require(Pump() == 0, "Hidden parent left a retry pending");
                area.Visibility = Visibility.Visible; bar.Height = 36; Layout(area); Pump();
                Require(scroller.Margin.Top == 36, "Visibility restoration lost inset");
            });
            Record("detach cancels and reattach resumes", () =>
            {
                bar.Height = 0; Layout(area); fixture.Start(bar, area);
                area.Children.Remove(bar); Pump();
                Require(!bar.IsLoaded, "Detached bar remained loaded");
                bar.Height = 42; area.Children.Add(bar); Layout(area); Pump();
                Require(scroller.Margin.Top == 42, "Reattachment lost inset");
            });
            Record("first placement before load retains styling and resumes", () =>
            {
                var (newArea, newScroller, newBar) = Elements(27);
                fixture.Start(newBar, newArea); Pump();
                Require(newBar.HorizontalAlignment == HorizontalAlignment.Stretch, "Initial styling changed");
                host.RootVisual = newArea; Layout(newArea); Pump();
                Require(newBar.IsLoaded && newScroller.Margin.Top == 27, "Load did not resume positioning");
                host.RootVisual = area; Layout(area); Pump();
                fixture.Start(bar, area);
            });
            Record("theme switch clears inset and pending retry", () =>
            {
                bar.Height = 0; Layout(area); fixture.Start(bar, area);
                ThemeManager.Current = Theme.Dark; fixture.Start(bar, area);
                Require(Pump() == 0, "Theme switch left a retry pending");
                Require(scroller.Margin.Top == 0, "Theme switch retained 98SE inset");
                Require(bar.HorizontalAlignment == HorizontalAlignment.Right, "Floating placement changed");
            });
            Require(Application.Current is null && !IsWindowVisible(host.Handle), "Fixture isolation changed");
            File.WriteAllText(args[0], JsonSerializer.Serialize(new { Results, visibleWindows = 0, applicationCreated = false }, new JsonSerializerOptions { WriteIndented = true }));
            return 0;
        }
        catch (Exception ex) { Console.Error.WriteLine(ex); return 1; }
    }
}
