Option Strict On
Option Explicit On

Imports System
Imports Nexamas.UI.General
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Components

    ''' <summary>
    ''' Official Media content layout resolver. The outer PanelShell owns shell, shadow,
    ''' radius, and frame; this resolver only partitions the shell content into Media regions.
    ''' It is intentionally responsive: chrome is compressed before any region is hidden,
    ''' and emergency layouts preserve a usable viewport instead of letting fixed rails collide.
    ''' </summary>
    Friend NotInheritable Class MASMediaContainerLayoutResolver
        Private Sub New()
        End Sub

        Friend Shared Function Resolve(bounds As SKRect,
                                       dpi As Single,
                                       toolbarChromeAllowed As Boolean,
                                       reserveToolbarChrome As Boolean,
                                       showFooter As Boolean,
                                       toolbarActionCount As Integer,
                                       Optional toolbarGroupBreakCount As Integer = 1,
                                       Optional chromeScale As Single = 1.0F) As MASMediaContainerLayoutPlan
            dpi = PixelSnap.SafeDpi(dpi)
            Dim scale As Single = SanitizeChromeScale(chromeScale)
            Dim unit As Single = ResolveChromeUnitPx(dpi, scale)

            Dim surface As SKRect = PixelSnap.SnapRectHalf(bounds)

            Dim plan As New MASMediaContainerLayoutPlan With {
                .Surface = surface,
                .PreviewFrame = SKRect.Empty,
                .Toolbar = SKRect.Empty,
                .Stage = SKRect.Empty,
                .Viewport = SKRect.Empty,
                .Footer = SKRect.Empty,
                .ToolbarFloating = False
            }

            If surface.Width <= 1.0F OrElse surface.Height <= 1.0F Then Return plan

            Dim actualReserveToolbar As Boolean = toolbarChromeAllowed AndAlso reserveToolbarChrome
            Dim toolbarHeight As Single = If(actualReserveToolbar,
                                             ResolvePinnedToolbarHeightPx(surface, dpi, scale, toolbarActionCount, toolbarGroupBreakCount),
                                             0.0F)
            If toolbarHeight <= 1.0F Then actualReserveToolbar = False

            Dim footerHeight As Single = ResolveFooterHeightPx(surface, dpi, scale, showFooter, toolbarHeight)
            Dim reservedToolbarBottom As Single = surface.Top

            If actualReserveToolbar Then
                plan.Toolbar = PixelSnap.SnapRectHalf(New SKRect(surface.Left,
                                                                 surface.Top,
                                                                 surface.Right,
                                                                 Math.Min(surface.Bottom, surface.Top + toolbarHeight)))
                plan.ToolbarFloating = False
                reservedToolbarBottom = plan.Toolbar.Bottom
            End If

            Dim previewFrame As SKRect = ResolvePreviewFrameRect(surface, dpi, scale, reservedToolbarBottom)
            Dim previewContent As SKRect = ResolvePreviewContentRect(previewFrame, dpi, scale)

            plan.PreviewFrame = previewFrame

            If footerHeight > 1.0F AndAlso previewContent.Height > MediaContainerTokens.EmergencyPreviewHeightDip * unit Then
                Dim footerTop As Single = Math.Max(previewContent.Top, previewContent.Bottom - Math.Min(footerHeight, Math.Max(0.0F, previewContent.Height - MediaContainerTokens.EmergencyPreviewHeightDip * unit)))
                If previewContent.Bottom - footerTop > 1.0F Then
                    plan.Footer = PixelSnap.SnapRectHalf(New SKRect(previewContent.Left,
                                                                    footerTop,
                                                                    previewContent.Right,
                                                                    previewContent.Bottom))
                End If
            End If

            Dim stageTop As Single = previewContent.Top
            Dim stageBottom As Single = If(plan.Footer.Width > 1.0F AndAlso plan.Footer.Height > 1.0F, plan.Footer.Top, previewContent.Bottom)
            If stageBottom < stageTop Then stageBottom = stageTop

            plan.Stage = PixelSnap.SnapRectHalf(New SKRect(previewContent.Left, stageTop, previewContent.Right, stageBottom))
            plan.Viewport = MASMediaViewportLayoutResolver.ResolveViewportRect(plan.Stage, dpi)

            If toolbarChromeAllowed AndAlso Not actualReserveToolbar Then
                plan.ToolbarFloating = True
                plan.Toolbar = MASMediaToolbarLayoutResolver.ResolveFloatingToolbarRect(plan.Stage, dpi, toolbarActionCount, toolbarGroupBreakCount, scale)
            End If

            Return plan
        End Function

        Private Shared Function ResolvePinnedToolbarHeightPx(surface As SKRect,
                                                            dpi As Single,
                                                            chromeScale As Single,
                                                            toolbarActionCount As Integer,
                                                            toolbarGroupBreakCount As Integer) As Single
            If surface.Width <= 1.0F OrElse surface.Height <= 1.0F Then Return 0.0F
            dpi = PixelSnap.SafeDpi(dpi)
            Dim unit As Single = ResolveChromeUnitPx(dpi, chromeScale)

            Dim widthDip As Single = surface.Width / unit
            Dim heightDip As Single = surface.Height / unit
            If widthDip < MediaContainerTokens.MinimalChromeWidthDip OrElse heightDip < MediaContainerTokens.MinimalChromeHeightDip Then Return 0.0F

            Dim desiredHeightDip As Single
            If widthDip >= MediaContainerTokens.DenseWidthBreakpointDip AndAlso heightDip >= MediaContainerTokens.DenseHeightBreakpointDip Then
                desiredHeightDip = MediaToolbarTokens.HeightDip
            ElseIf widthDip >= MediaContainerTokens.CompactWidthBreakpointDip AndAlso heightDip >= MediaContainerTokens.CompactHeightBreakpointDip Then
                desiredHeightDip = MediaToolbarTokens.DenseHeightDip
            Else
                desiredHeightDip = MediaToolbarTokens.CompactHeightDip
            End If

            Dim specs As MASMediaButtonSpec() = CreateSizingSpecs(toolbarActionCount, toolbarGroupBreakCount)
            If specs.Length = 0 Then Return 0.0F

            Dim sideInset As Single = ResolvePinnedToolbarSideInsetPx(surface, dpi, chromeScale)
            Dim laneWidth As Single = Math.Max(0.0F, surface.Width - sideInset * 2.0F)
            Dim desiredHeight As Single = desiredHeightDip * unit

            If Not MASMediaButtonStripLayoutResolver.CanFitAll(specs, laneWidth, desiredHeight, dpi, False, chromeScale) Then
                desiredHeight = MediaToolbarTokens.MinimalPinnedHeightDip * unit
                If Not MASMediaButtonStripLayoutResolver.CanFitAll(specs, laneWidth, desiredHeight, dpi, False, chromeScale) Then Return 0.0F
            End If

            Dim maxChromeHeight As Single = Math.Max(MediaToolbarTokens.MinimalPinnedHeightDip * unit,
                                                     surface.Height - MediaContainerTokens.MinimalPreviewHeightDip * unit)
            desiredHeight = Math.Min(desiredHeight, maxChromeHeight)
            If desiredHeight < MediaToolbarTokens.MinimalPinnedHeightDip * unit Then Return 0.0F

            Return Math.Max(1.0F, desiredHeight)
        End Function

        Private Shared Function ResolveFooterHeightPx(surface As SKRect,
                                                      dpi As Single,
                                                      chromeScale As Single,
                                                      showFooter As Boolean,
                                                      toolbarHeightPx As Single) As Single
            If Not showFooter Then Return 0.0F
            If surface.Width <= 1.0F OrElse surface.Height <= 1.0F Then Return 0.0F
            dpi = PixelSnap.SafeDpi(dpi)
            Dim unit As Single = ResolveChromeUnitPx(dpi, chromeScale)

            Dim widthDip As Single = surface.Width / unit
            Dim heightDip As Single = surface.Height / unit
            If widthDip < MediaFooterTokens.HideWidthThresholdDip OrElse heightDip < MediaFooterTokens.HideHeightThresholdDip Then Return 0.0F

            Dim requestedDip As Single
            If widthDip >= MediaContainerTokens.DenseWidthBreakpointDip AndAlso heightDip >= MediaContainerTokens.DenseHeightBreakpointDip Then
                requestedDip = MediaFooterTokens.HeightDip
            ElseIf widthDip >= MediaContainerTokens.CompactWidthBreakpointDip AndAlso heightDip >= MediaContainerTokens.CompactHeightBreakpointDip Then
                requestedDip = MediaFooterTokens.DenseHeightDip
            ElseIf widthDip >= MediaContainerTokens.MinimalChromeWidthDip AndAlso heightDip >= MediaContainerTokens.MinimalChromeHeightDip Then
                requestedDip = MediaFooterTokens.CompactHeightDip
            Else
                requestedDip = MediaFooterTokens.MinimalHeightDip
            End If

            Dim requested As Single = requestedDip * unit
            Dim remainingAfterChrome As Single = surface.Height - toolbarHeightPx
            Dim maxFooterHeight As Single = Math.Max(0.0F, remainingAfterChrome - MediaContainerTokens.EmergencyPreviewHeightDip * unit)
            requested = Math.Min(requested, maxFooterHeight)
            If requested < MediaFooterTokens.MinimalHeightDip * unit Then Return 0.0F
            Return requested
        End Function

        Private Shared Function CreateSizingSpecs(actionCount As Integer,
                                                  groupBreakCount As Integer) As MASMediaButtonSpec()
            Dim safeActionCount As Integer = Math.Max(0, actionCount)
            If safeActionCount = 0 Then Return Array.Empty(Of MASMediaButtonSpec)()

            Dim safeBreaks As Integer = Math.Max(0, Math.Min(Math.Max(0, safeActionCount - 1), groupBreakCount))
            Dim result(safeActionCount - 1) As MASMediaButtonSpec
            Dim groupIndex As Integer = 0
            Dim firstBreakIndex As Integer = safeActionCount - safeBreaks
            For i As Integer = 0 To safeActionCount - 1
                If i > 0 AndAlso i >= firstBreakIndex Then groupIndex += 1
                result(i) = New MASMediaButtonSpec(i + 1, String.Empty, groupIndex, MASMediaButtonGlyphKind.None)
            Next
            Return result
        End Function

        Private Shared Function ResolvePinnedToolbarSideInsetPx(surface As SKRect,
                                                               dpi As Single,
                                                               chromeScale As Single) As Single
            dpi = PixelSnap.SafeDpi(dpi)
            Dim unit As Single = ResolveChromeUnitPx(dpi, chromeScale)
            Dim widthDip As Single = surface.Width / unit
            If widthDip <= MediaContainerTokens.MinimalChromeWidthDip Then Return MediaToolbarTokens.MinimalSideInsetDip * unit
            If widthDip <= MediaContainerTokens.CompactWidthBreakpointDip Then Return MediaToolbarTokens.CompactSideInsetDip * unit
            Return MediaToolbarTokens.SideInsetDip * unit
        End Function

        Private Shared Function ResolvePreviewFrameRect(surface As SKRect,
                                                        dpi As Single,
                                                        chromeScale As Single,
                                                        reservedToolbarBottom As Single) As SKRect
            If surface.Width <= 1.0F OrElse surface.Height <= 1.0F Then Return SKRect.Empty
            dpi = PixelSnap.SafeDpi(dpi)
            Dim unit As Single = ResolveChromeUnitPx(dpi, chromeScale)

            Dim sideInsetDip As Single = ResolvePreviewSideInsetDip(surface, dpi, chromeScale)
            Dim bottomInsetDip As Single = ResolvePreviewBottomInsetDip(surface, dpi, chromeScale)
            Dim topGapAfterToolbarDip As Single = ResolvePreviewTopGapAfterToolbarDip(surface, dpi, chromeScale)

            Dim sideInset As Single = Math.Max(0.0F, sideInsetDip * unit)
            Dim bottomInset As Single = Math.Max(0.0F, bottomInsetDip * unit)
            Dim topInset As Single = sideInset

            If reservedToolbarBottom > surface.Top + 0.5F Then
                topInset = (reservedToolbarBottom - surface.Top) + topGapAfterToolbarDip * unit
            End If

            Dim candidate As New SKRect(surface.Left + sideInset,
                                        surface.Top + topInset,
                                        surface.Right - sideInset,
                                        surface.Bottom - bottomInset)

            If candidate.Width <= 1.0F OrElse candidate.Height <= 1.0F Then
                candidate = New SKRect(surface.Left + sideInset,
                                       surface.Top + sideInset,
                                       surface.Right - sideInset,
                                       surface.Bottom - bottomInset)
            End If

            If candidate.Width <= 1.0F OrElse candidate.Height <= 1.0F Then Return surface
            Return PixelSnap.SnapRectHalf(candidate)
        End Function

        Private Shared Function ResolvePreviewContentRect(previewFrame As SKRect,
                                                          dpi As Single,
                                                          chromeScale As Single) As SKRect
            If previewFrame.Width <= 1.0F OrElse previewFrame.Height <= 1.0F Then Return SKRect.Empty
            dpi = PixelSnap.SafeDpi(dpi)
            Dim unit As Single = ResolveChromeUnitPx(dpi, chromeScale)

            Dim safeInset As Single = Math.Max(0.0F, MediaContainerTokens.PreviewFrameStrokeSafeInsetDip * unit)
            Dim content As SKRect = PixelSnap.InsetAll(previewFrame, safeInset)
            If content.Width <= 1.0F OrElse content.Height <= 1.0F Then Return previewFrame
            Return PixelSnap.SnapRect(content, dpi)
        End Function

        Private Shared Function ResolvePreviewSideInsetDip(surface As SKRect,
                                                          dpi As Single,
                                                          chromeScale As Single) As Single
            Dim unit As Single = ResolveChromeUnitPx(dpi, chromeScale)
            Dim widthDip As Single = surface.Width / unit
            Dim heightDip As Single = surface.Height / unit
            If widthDip <= MediaContainerTokens.MinimalChromeWidthDip OrElse heightDip <= MediaContainerTokens.MinimalChromeHeightDip Then Return MediaContainerTokens.EmergencyPreviewFrameInsetDip
            If widthDip <= MediaContainerTokens.CompactWidthBreakpointDip OrElse heightDip <= MediaContainerTokens.CompactHeightBreakpointDip Then Return MediaContainerTokens.CompactPreviewFrameInsetDip
            If widthDip <= MediaContainerTokens.DenseWidthBreakpointDip OrElse heightDip <= MediaContainerTokens.DenseHeightBreakpointDip Then Return MediaContainerTokens.DensePreviewFrameInsetDip
            Return MediaContainerTokens.PreviewFrameInsetDip
        End Function

        Private Shared Function ResolvePreviewBottomInsetDip(surface As SKRect,
                                                            dpi As Single,
                                                            chromeScale As Single) As Single
            Dim unit As Single = ResolveChromeUnitPx(dpi, chromeScale)
            Dim widthDip As Single = surface.Width / unit
            Dim heightDip As Single = surface.Height / unit
            If widthDip <= MediaContainerTokens.MinimalChromeWidthDip OrElse heightDip <= MediaContainerTokens.MinimalChromeHeightDip Then Return MediaContainerTokens.EmergencyPreviewFrameBottomInsetDip
            If widthDip <= MediaContainerTokens.CompactWidthBreakpointDip OrElse heightDip <= MediaContainerTokens.CompactHeightBreakpointDip Then Return MediaContainerTokens.CompactPreviewFrameBottomInsetDip
            If widthDip <= MediaContainerTokens.DenseWidthBreakpointDip OrElse heightDip <= MediaContainerTokens.DenseHeightBreakpointDip Then Return MediaContainerTokens.DensePreviewFrameBottomInsetDip
            Return MediaContainerTokens.PreviewFrameBottomInsetDip
        End Function

        Private Shared Function ResolvePreviewTopGapAfterToolbarDip(surface As SKRect,
                                                                   dpi As Single,
                                                                   chromeScale As Single) As Single
            Dim unit As Single = ResolveChromeUnitPx(dpi, chromeScale)
            Dim widthDip As Single = surface.Width / unit
            Dim heightDip As Single = surface.Height / unit
            If widthDip <= MediaContainerTokens.MinimalChromeWidthDip OrElse heightDip <= MediaContainerTokens.MinimalChromeHeightDip Then Return MediaContainerTokens.EmergencyPreviewFrameTopGapAfterToolbarDip
            If widthDip <= MediaContainerTokens.CompactWidthBreakpointDip OrElse heightDip <= MediaContainerTokens.CompactHeightBreakpointDip Then Return MediaContainerTokens.CompactPreviewFrameTopGapAfterToolbarDip
            If widthDip <= MediaContainerTokens.DenseWidthBreakpointDip OrElse heightDip <= MediaContainerTokens.DenseHeightBreakpointDip Then Return MediaContainerTokens.DensePreviewFrameTopGapAfterToolbarDip
            Return MediaContainerTokens.PreviewFrameTopGapAfterToolbarDip
        End Function

        Private Shared Function ResolveChromeUnitPx(dpi As Single,
                                                    chromeScale As Single) As Single
            Dim safeDpi As Single = PixelSnap.SafeDpi(dpi)
            Return safeDpi * SanitizeChromeScale(chromeScale)
        End Function

        Private Shared Function SanitizeChromeScale(value As Single) As Single
            If Single.IsNaN(value) OrElse Single.IsInfinity(value) OrElse value <= 0.0F Then Return 1.0F
            Return Math.Min(1.24F, Math.Max(0.72F, value))
        End Function
    End Class

End Namespace
