// Alias Console to MockConsole so we don't accidentally use Console

namespace Terminal.Gui.InputTests;

public class ResponderTests
{
    // Generic lifetime (IDisposable) tests
    [Fact]
    [TestRespondersDisposed]
    public void Dispose_Works ()
    {
        var r = new Responder ();
#if DEBUG_IDISPOSABLE
        Assert.Single (Responder.Instances);
#endif

        r.Dispose ();
#if DEBUG_IDISPOSABLE
        Assert.Empty (Responder.Instances);
#endif
    }

    [Fact]
    public void Disposing_Event_Notify_All_Subscribers_On_The_First_Container ()
    {
    #if DEBUG_IDISPOSABLE
        // Only clear before because need to test after assert
        Responder.Instances.Clear ();
    #endif

        var container1 = new View { Id = "Container1" };
        var count = 0;

        var view = new View { Id = "View" };
        view.Disposing += View_Disposing;
        container1.Add (view);
        Assert.Equal (container1, view.SuperView);

        void View_Disposing (object sender, EventArgs e)
        {
            count++;
            Assert.Equal (view, sender);
            container1.Remove ((View)sender);
        }

        Assert.Single (container1.Subviews);

        var container2 = new View { Id = "Container2" };

        container2.Add (view);
        Assert.Equal (container2, view.SuperView);
        Assert.Equal (container1.Subviews.Count, container2.Subviews.Count);
        container2.Dispose ();

        Assert.Empty (container1.Subviews);
        Assert.Empty (container2.Subviews);
        Assert.Equal (1, count);
        Assert.Null (view.SuperView);

        container1.Dispose ();

    #if DEBUG_IDISPOSABLE
        Assert.Empty (Responder.Instances);
    #endif
    }

    [Fact]
    public void Disposing_Event_Notify_All_Subscribers_On_The_Second_Container ()
    {
    #if DEBUG_IDISPOSABLE
        // Only clear before because need to test after assert
        Responder.Instances.Clear ();
    #endif

        var container1 = new View { Id = "Container1" };

        var view = new View { Id = "View" };
        container1.Add (view);
        Assert.Equal (container1, view.SuperView);
        Assert.Single (container1.Subviews);

        var container2 = new View { Id = "Container2" };
        var count = 0;

        view.Disposing += View_Disposing;
        container2.Add (view);
        Assert.Equal (container2, view.SuperView);

        void View_Disposing (object sender, EventArgs e)
        {
            count++;
            Assert.Equal (view, sender);
            container2.Remove ((View)sender);
        }

        Assert.Equal (container1.Subviews.Count, container2.Subviews.Count);
        container1.Dispose ();

        Assert.Empty (container1.Subviews);
        Assert.Empty (container2.Subviews);
        Assert.Equal (1, count);
        Assert.Null (view.SuperView);

        container2.Dispose ();

    #if DEBUG_IDISPOSABLE
        Assert.Empty (Responder.Instances);
    #endif
    }

    [Fact]
    [TestRespondersDisposed]
    public void IsOverridden_False_IfNotOverridden ()
    {
        // MouseEvent IS defined on Responder but NOT overridden
        Assert.False (Responder.IsOverridden (new Responder (), "OnMouseEvent"));

        // MouseEvent is defined on Responder and NOT overrident on View
        Assert.False (
                      Responder.IsOverridden (
                                              new View { Text = "View does not override OnMouseEvent" },
                                              "OnMouseEvent"
                                             )
                     );

        Assert.False (
                      Responder.IsOverridden (
                                              new DerivedView { Text = "DerivedView does not override OnMouseEvent" },
                                              "OnMouseEvent"
                                             )
                     );

        // MouseEvent is NOT defined on DerivedView 
        Assert.False (
                      Responder.IsOverridden (
                                              new DerivedView { Text = "DerivedView does not override OnMouseEvent" },
                                              "OnMouseEvent"
                                             )
                     );

        // OnKeyDown is defined on View and NOT overrident on Button
        Assert.False (
                      Responder.IsOverridden (
                                              new Button { Text = "Button does not override OnKeyDown" },
                                              "OnKeyDown"
                                             )
                     );

#if DEBUG_IDISPOSABLE

        // HACK: Force clean up of Responders to avoid having to Dispose all the Views created above.
        Responder.Instances.Clear ();
        Assert.Empty (Responder.Instances);
#endif
    }

    [Fact]
    [TestRespondersDisposed]
    public void IsOverridden_True_IfOverridden ()
    {
        // MouseEvent is defined on Responder IS overriden on ScrollBarView (but not View)
        Assert.True (
                     Responder.IsOverridden (
                                             new ScrollBarView { Text = "ScrollBarView overrides OnMouseEvent" },
                                             "OnMouseEvent"
                                            )
                    );

        // OnKeyDown is defined on View
        Assert.False (Responder.IsOverridden (new View { Text = "View overrides OnKeyDown" }, "OnKeyDown"));

        // OnKeyDown is defined on DerivedView
        Assert.True (
                     Responder.IsOverridden (
                                             new DerivedView { Text = "DerivedView overrides OnKeyDown" },
                                             "OnKeyDown"
                                            )
                    );

        // ScrollBarView overrides both MouseEvent (from Responder) and Redraw (from View)
        Assert.True (
                     Responder.IsOverridden (
                                             new ScrollBarView { Text = "ScrollBarView overrides OnMouseEvent" },
                                             "OnMouseEvent"
                                            )
                    );

        Assert.True (
                     Responder.IsOverridden (
                                             new ScrollBarView { Text = "ScrollBarView overrides OnDrawContent" },
                                             "OnDrawContent"
                                            )
                    );
#if DEBUG_IDISPOSABLE

        // HACK: Force clean up of Responders to avoid having to Dispose all the Views created above.
        Responder.Instances.Clear ();
        Assert.Empty (Responder.Instances);
#endif
    }

    [Fact]
    public void KeyPressed_Handled_True_Cancels_KeyPress ()
    {
        var r = new View ();
        var args = new Key { KeyCode = KeyCode.Null };

        Assert.False (r.OnKeyDown (args));
        Assert.False (args.Handled);

        r.KeyDown += (s, a) => a.Handled = true;
        Assert.True (r.OnKeyDown (args));
        Assert.True (args.Handled);

        r.Dispose ();
    }

    [Fact]
    [TestRespondersDisposed]
    public void New_Initializes ()
    {
        var r = new Responder ();
        Assert.NotNull (r);
        Assert.Equal ("Terminal.Gui.Responder", r.ToString ());
        r.Dispose ();
    }

    [Fact]
    [TestRespondersDisposed]
    public void New_Methods_Return_False ()
    {
        var r = new View ();

        //Assert.False (r.OnKeyDown (new KeyEventArgs () { Key = Key.Unknown }));
        Assert.False (r.OnKeyDown (new Key { KeyCode = KeyCode.Null }));
        Assert.False (r.OnKeyUp (new Key { KeyCode = KeyCode.Null }));
        Assert.False (r.NewMouseEvent (new MouseEvent { Flags = MouseFlags.AllEvents }));
        Assert.False (r.NewMouseEnterEvent (new MouseEvent { Flags = MouseFlags.AllEvents }));
        Assert.False (r.NewMouseLeaveEvent (new MouseEvent { Flags = MouseFlags.AllEvents }));

        var v = new View ();
        Assert.False (r.OnEnter (v));
        v.Dispose ();

        v = new View ();
        Assert.False (r.OnLeave (v));
        v.Dispose ();

        r.Dispose ();
    }

    [Fact]
    public void Responder_Not_Notifying_Dispose ()
    {
        // Only clear before because need to test after assert
    #if DEBUG_IDISPOSABLE
        Responder.Instances.Clear ();
    #endif
        var container1 = new View { Id = "Container1" };

        var view = new View { Id = "View" };
        container1.Add (view);
        Assert.Equal (container1, view.SuperView);

        Assert.Single (container1.Subviews);

        var container2 = new View { Id = "Container2" };

        container2.Add (view);
        Assert.Equal (container2, view.SuperView);
        Assert.Equal (container1.Subviews.Count, container2.Subviews.Count);
        container1.Dispose ();

        Assert.Empty (container1.Subviews);
        Assert.NotEmpty (container2.Subviews);
        Assert.Single (container2.Subviews);
        Assert.Null (view.SuperView);

        // Trying access disposed properties
    #if DEBUG_IDISPOSABLE
        Assert.True (container2.Subviews [0].WasDisposed);
    #endif
        Assert.False (container2.Subviews [0].CanFocus);
        Assert.Null (container2.Subviews [0].Margin);
        Assert.Null (container2.Subviews [0].Border);
        Assert.Null (container2.Subviews [0].Padding);
        Assert.Null (view.SuperView);

        container2.Dispose ();

    #if DEBUG_IDISPOSABLE
        Assert.Empty (Responder.Instances);
    #endif
    }

    public class DerivedView : View
    {
        public override bool OnKeyDown (Key keyEvent) { return true; }
    }
}
