using Avalonia;
using LanMountainDesktop.DesktopEditing;
using Xunit;

namespace LanMountainDesktop.Tests;

public sealed class DesktopPlacementMathTests
{
    [Fact]
    public void ComputeDragStartThreshold_UsesFloorAndCellScale()
    {
        Assert.Equal(10d, DesktopPlacementMath.ComputeDragStartThreshold(24));
        Assert.Equal(14.4d, DesktopPlacementMath.ComputeDragStartThreshold(80), 3);
    }

    [Fact]
    public void HasExceededThreshold_OnlyReturnsTrueAfterEnoughMovement()
    {
        var start = new Point(20, 20);

        Assert.False(DesktopPlacementMath.HasExceededThreshold(start, new Point(27, 25), 10));
        Assert.True(DesktopPlacementMath.HasExceededThreshold(start, new Point(31, 20), 10));
    }

    [Fact]
    public void OcclusionHelpers_DetectPointAndRectOverlap()
    {
        var libraryBounds = new Rect(100, 100, 200, 160);

        Assert.True(DesktopPlacementMath.IsOccludedByComponentLibrary(new Point(120, 150), libraryBounds));
        Assert.False(DesktopPlacementMath.IsOccludedByComponentLibrary(new Point(80, 90), libraryBounds));
        Assert.True(DesktopPlacementMath.IsOccludedByComponentLibrary(new Rect(250, 120, 120, 80), libraryBounds));
        Assert.False(DesktopPlacementMath.IsOccludedByComponentLibrary(new Rect(10, 10, 40, 40), libraryBounds));
    }

    [Fact]
    public void TryGetSnappedCell_ClampsInsideGridBounds()
    {
        var grid = new DesktopGridGeometry(
            Origin: default,
            CellSize: 80,
            CellGap: 8,
            ColumnCount: 4,
            RowCount: 5);

        var result = DesktopPlacementMath.TryGetSnappedCell(
            grid,
            pointerInViewport: new Point(490, 520),
            pointerOffset: new Point(10, 10),
            widthCells: 2,
            heightCells: 3,
            out var column,
            out var row);

        Assert.True(result);
        Assert.Equal(2, column);
        Assert.Equal(2, row);
    }

    [Fact]
    public void GetCellRect_MapsCellsToPixelRect()
    {
        var grid = new DesktopGridGeometry(
            Origin: new Point(12, 24),
            CellSize: 80,
            CellGap: 8,
            ColumnCount: 6,
            RowCount: 8);

        var rect = DesktopPlacementMath.GetCellRect(grid, column: 2, row: 3, widthCells: 2, heightCells: 3);

        Assert.Equal(188, rect.X, 3);
        Assert.Equal(288, rect.Y, 3);
        Assert.Equal(168, rect.Width, 3);
        Assert.Equal(256, rect.Height, 3);
    }

    [Fact]
    public void Session_DoesNotCommitWhilePointerIsStillInsideLibrary()
    {
        var session = DesktopEditSession.CreatePendingNew(
            componentId: "demo",
            pageIndex: 0,
            widthCells: 2,
            heightCells: 2,
            startPointerInViewport: new Point(80, 80),
            pointerOffsetInViewport: new Point(60, 60),
            componentLibraryBounds: new Rect(0, 0, 220, 300));

        session = session.WithCurrentPointer(new Point(130, 150));

        Assert.True(session.HasExceededThreshold(DesktopPlacementMath.ComputeDragStartThreshold(80)));
        Assert.True(session.IsPointerInsideComponentLibrary());
        Assert.False(session.CanCommit);
    }

    [Fact]
    public void Session_ResizePreviewStillBlocksWhenPointerRemainsInsideLibrary()
    {
        var session = DesktopEditSession.CreateResizingExisting(
            componentId: "demo",
            placementId: "placement-1",
            pageIndex: 0,
            widthCells: 2,
            heightCells: 2,
            startPointerInViewport: new Point(80, 80),
            componentLibraryBounds: new Rect(0, 0, 220, 300))
            .WithCurrentPointer(new Point(130, 150));

        Assert.True(session.IsPointerInsideComponentLibrary());
        Assert.False(session.CanCommit);
    }

    [Fact]
    public void HasCellPositionChanged_DetectsNoOpAndRealMoves()
    {
        Assert.False(DesktopPlacementMath.HasCellPositionChanged(2, 3, 2, 3));
        Assert.True(DesktopPlacementMath.HasCellPositionChanged(2, 3, 2, 4));
    }

    [Fact]
    public void HasCellSpanChanged_DetectsNoOpAndRealResizes()
    {
        Assert.False(DesktopPlacementMath.HasCellSpanChanged(2, 3, 2, 3));
        Assert.True(DesktopPlacementMath.HasCellSpanChanged(2, 3, 3, 3));
    }

    [Fact]
    public void CanCommitPlacement_BlocksWhenPlacementIsOccludedByLibrary()
    {
        var placementRect = new Rect(160, 110, 180, 140);
        var occludingLibraryBounds = new Rect(120, 80, 240, 220);
        var distantLibraryBounds = new Rect(420, 420, 80, 80);

        Assert.False(DesktopPlacementMath.CanCommitPlacement(placementRect, occludingLibraryBounds));
        Assert.True(DesktopPlacementMath.CanCommitPlacement(placementRect, distantLibraryBounds));
        Assert.True(DesktopPlacementMath.CanCommitPlacement(placementRect, componentLibraryBounds: null));
    }

    [Fact]
    public void Session_AllowsCommitWhenComponentLibraryBoundsAreCleared()
    {
        var pendingSession = DesktopEditSession.CreatePendingNew(
            componentId: "demo",
            pageIndex: 0,
            widthCells: 2,
            heightCells: 2,
            startPointerInViewport: new Point(80, 80),
            pointerOffsetInViewport: new Point(60, 60),
            componentLibraryBounds: null)
            .WithCurrentPointer(new Point(200, 180));

        Assert.True(pendingSession.HasExceededThreshold(DesktopPlacementMath.ComputeDragStartThreshold(80)));
        Assert.False(pendingSession.IsPointerInsideComponentLibrary());
        Assert.False(pendingSession.IsPreviewOccludedByComponentLibrary(new Rect(100, 100, 40, 40)));
        Assert.False(pendingSession.CanCommit);

        var resizeSession = DesktopEditSession.CreateResizingExisting(
            componentId: "demo",
            placementId: "placement-1",
            pageIndex: 0,
            widthCells: 2,
            heightCells: 2,
            startPointerInViewport: new Point(80, 80),
            componentLibraryBounds: null)
            .WithCurrentPointer(new Point(200, 180))
            .WithTargetCell(row: 2, column: 3);

        Assert.False(resizeSession.IsPointerInsideComponentLibrary());
        Assert.False(resizeSession.IsPreviewOccludedByComponentLibrary(new Rect(100, 100, 40, 40)));
        Assert.True(resizeSession.CanCommit);
    }
}
