Option Strict On
Option Explicit On

Imports System
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Components

    ''' <summary>
    ''' Friend-only viewport math for MASImageViewer. It keeps fitting, displayed bounds,
    ''' pan limits, and cursor anchored zoom in one deterministic place so the visual shell
    ''' does not own transform arithmetic.
    ''' </summary>
    Friend NotInheritable Class MASImageViewerViewportGeometry
        Private Sub New()
        End Sub

        Friend Shared Function ResolveFitZoom(contentRect As SKRect,
                                             imageWidth As Integer,
                                             imageHeight As Integer,
                                             rotationDegrees As Integer,
                                             Optional fitMode As MASImageViewerImageFitMode = MASImageViewerImageFitMode.Cover) As Single
            If contentRect.Width <= 1.0F OrElse contentRect.Height <= 1.0F Then Return ImageViewerTokens.DefaultZoom
            If imageWidth <= 0 OrElse imageHeight <= 0 Then Return ImageViewerTokens.DefaultZoom

            Dim rotated As Boolean = (rotationDegrees Mod 180) <> 0
            Dim displayWidth As Single = If(rotated, CSng(imageHeight), CSng(imageWidth))
            Dim displayHeight As Single = If(rotated, CSng(imageWidth), CSng(imageHeight))
            If displayWidth <= 0.0F OrElse displayHeight <= 0.0F Then Return ImageViewerTokens.DefaultZoom

            Dim fit As Single
            If fitMode = MASImageViewerImageFitMode.Contain Then
                ' Proof/review surfaces must display the entire bitmap inside the
                ' available viewport so generated PNG evidence is never cropped.
                fit = Math.Min(contentRect.Width / displayWidth, contentRect.Height / displayHeight)
            Else
                ' Historical media-preview behavior: fill the owned viewport edge-to-edge.
                fit = Math.Max(contentRect.Width / displayWidth, contentRect.Height / displayHeight)
            End If
            If Single.IsNaN(fit) OrElse Single.IsInfinity(fit) Then fit = ImageViewerTokens.DefaultZoom
            Return Math.Max(ImageViewerTokens.MinUsefulScale, Math.Min(ImageViewerTokens.MaxZoom, fit))
        End Function

        Friend Shared Function ResolveDisplayedRect(contentRect As SKRect,
                                                   imageWidth As Integer,
                                                   imageHeight As Integer,
                                                   rotationDegrees As Integer,
                                                   zoom As Single,
                                                   panX As Single,
                                                   panY As Single) As SKRect
            If imageWidth <= 0 OrElse imageHeight <= 0 OrElse contentRect.Width <= 1.0F OrElse contentRect.Height <= 1.0F Then Return SKRect.Empty

            Dim rotated As Boolean = (rotationDegrees Mod 180) <> 0
            Dim displayWidth As Single = If(rotated, CSng(imageHeight), CSng(imageWidth)) * zoom
            Dim displayHeight As Single = If(rotated, CSng(imageWidth), CSng(imageHeight)) * zoom
            Dim centerX As Single = contentRect.MidX + panX
            Dim centerY As Single = contentRect.MidY + panY
            Return New SKRect(centerX - displayWidth / 2.0F,
                              centerY - displayHeight / 2.0F,
                              centerX + displayWidth / 2.0F,
                              centerY + displayHeight / 2.0F)
        End Function

        Friend Shared Sub ClampPan(contentRect As SKRect,
                                   imageWidth As Integer,
                                   imageHeight As Integer,
                                   rotationDegrees As Integer,
                                   zoom As Single,
                                   ByRef panX As Single,
                                   ByRef panY As Single)
            If contentRect.Width <= 1.0F OrElse contentRect.Height <= 1.0F OrElse imageWidth <= 0 OrElse imageHeight <= 0 Then
                panX = 0.0F
                panY = 0.0F
                Return
            End If

            Dim rotated As Boolean = (rotationDegrees Mod 180) <> 0
            Dim displayWidth As Single = If(rotated, CSng(imageHeight), CSng(imageWidth)) * zoom
            Dim displayHeight As Single = If(rotated, CSng(imageWidth), CSng(imageHeight)) * zoom
            Dim maxX As Single = Math.Max(0.0F, (displayWidth - contentRect.Width) / 2.0F)
            Dim maxY As Single = Math.Max(0.0F, (displayHeight - contentRect.Height) / 2.0F)

            If maxX <= 0.5F Then
                panX = 0.0F
            Else
                panX = Math.Max(-maxX, Math.Min(maxX, panX))
            End If

            If maxY <= 0.5F Then
                panY = 0.0F
            Else
                panY = Math.Max(-maxY, Math.Min(maxY, panY))
            End If
        End Sub

        Friend Shared Function CanPan(contentRect As SKRect,
                                      imageWidth As Integer,
                                      imageHeight As Integer,
                                      rotationDegrees As Integer,
                                      zoom As Single) As Boolean
            If contentRect.Width <= 1.0F OrElse contentRect.Height <= 1.0F OrElse imageWidth <= 0 OrElse imageHeight <= 0 Then Return False
            Dim displayed As SKRect = ResolveDisplayedRect(contentRect, imageWidth, imageHeight, rotationDegrees, zoom, 0.0F, 0.0F)
            Return displayed.Width > contentRect.Width + 0.5F OrElse displayed.Height > contentRect.Height + 0.5F
        End Function

        Friend Shared Sub ApplyCursorAnchoredZoom(contentRect As SKRect,
                                                  anchorPx As SKPoint,
                                                  oldZoom As Single,
                                                  newZoom As Single,
                                                  ByRef panX As Single,
                                                  ByRef panY As Single)
            If contentRect.Width <= 1.0F OrElse contentRect.Height <= 1.0F Then Return
            If oldZoom <= 0.0F OrElse Single.IsNaN(oldZoom) OrElse Single.IsInfinity(oldZoom) Then Return
            If newZoom <= 0.0F OrElse Single.IsNaN(newZoom) OrElse Single.IsInfinity(newZoom) Then Return

            Dim oldCenterX As Single = contentRect.MidX + panX
            Dim oldCenterY As Single = contentRect.MidY + panY
            Dim ratio As Single = newZoom / oldZoom

            Dim newCenterX As Single = anchorPx.X - (anchorPx.X - oldCenterX) * ratio
            Dim newCenterY As Single = anchorPx.Y - (anchorPx.Y - oldCenterY) * ratio
            panX = newCenterX - contentRect.MidX
            panY = newCenterY - contentRect.MidY
        End Sub
    End Class

End Namespace
