Option Strict On
Option Explicit On

Imports System.Collections.Generic
Imports Nexamas.UI.Components
Imports Nexamas.UI.General
Imports Nexamas.UI.Scroll
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Rendering

    Friend NotInheritable Class MessageBoxLayoutEngine

        Private Sub New()
        End Sub

        Friend Shared Function Compute(panelRectPx As SKRect,
                               dpi As Single,
                               request As MessageBoxRequest,
                               measure As MessageBoxMeasureResult,
                               metrics As MessageBoxMetrics) As MessageBoxLayoutResult

            Dim result As New MessageBoxLayoutResult()

            If panelRectPx.IsEmpty OrElse panelRectPx.Width <= 0.0F OrElse panelRectPx.Height <= 0.0F Then Return result
            If request Is Nothing Then request = New MessageBoxRequest()
            If measure Is Nothing Then Return result

            If request.MetricsOverride IsNot Nothing Then
                metrics = request.MetricsOverride
            End If

            If metrics Is Nothing Then
                metrics = MessageBoxMetricsDefaults.Create()
            End If

            dpi = PixelSnap.SafeDpi(dpi)
            panelRectPx = PixelSnap.SnapRectHalf(panelRectPx)

            Dim shellSpec As MASPanelShellSpec =
                MessageBoxShellAdapter.CreateSpec(panelRectPx, request, New MessageBoxInteractionState())

            Dim shellResult As MASPanelShellResult =
             MASPanelShellRenderer.Measure(
    dpi:=dpi,
    spec:=shellSpec)

            If shellResult Is Nothing OrElse shellResult.ContentRectPx.IsEmpty Then Return result

            Dim hostRect As SKRect = shellResult.ContentRectPx

            result.PanelRect = panelRectPx
            result.ShellResult = shellResult
            result.ShellContentRect = hostRect

            ' Header geometry is owned by MASPanelShell.
            ' These are kept only for hit-testing/back-compat.
            result.HeaderRect = shellResult.HeaderRectPx
            result.IconRect = shellResult.HeaderIconRectPx
            result.TitleRect = shellResult.HeaderTitleRectPx
            result.CloseRect = shellResult.HeaderCloseRectPx
            result.HeaderHeight = shellResult.HeaderRectPx.Height

            result.CompanyIconSize = measure.CompanyIconSize
            result.CloseButtonSize = shellResult.HeaderCloseRectPx.Width
            result.ButtonsRowCount = measure.ButtonRowsCount
            result.HasScrollableContent = measure.HasScrollableContent
            result.MaxContentScrollOffsetY = measure.MaxContentScrollOffsetY

            result.TitleLines.Clear()
            result.TitleLines.AddRange(measure.TitleLines)

            result.MessageLines.Clear()
            result.MessageLines.AddRange(measure.MessageLines)

            result.DetailLines.Clear()
            result.DetailLines.AddRange(measure.DetailLines)

            Dim contentTopGap As Single = metrics.ContentTopGap * dpi
            Dim contentBottomGap As Single = metrics.ContentBottomGap * dpi
            Dim detailTopGap As Single = metrics.DetailTopGap * dpi
            Dim buttonsBottomGap As Single = metrics.ButtonsBottomGap * dpi
            Dim buttonGap As Single = metrics.ButtonsColumnGap * dpi
            Dim buttonsRowGap As Single = metrics.ButtonsRowGap * dpi
            Dim buttonHeight As Single = metrics.ButtonHeight * dpi
            Dim contentTopPad As Single = MessageBoxTokens.ContentTopPadDip * dpi

            Dim currentTop As Single = hostRect.Top

            If measure.CompanyIconSize > 0.0F Then
                Dim cx As Single = hostRect.MidX
                Dim half As Single = measure.CompanyIconSize * 0.5F

                result.CompanyIconRect = PixelSnap.SnapRectHalf(
                    New SKRect(
                        cx - half,
                        currentTop,
                        cx + half,
                        currentTop + measure.CompanyIconSize))

                currentTop = result.CompanyIconRect.Bottom + (metrics.CompanyIconBottomGap * dpi)
            Else
                result.CompanyIconRect = SKRect.Empty
            End If

            Dim hasContent As Boolean =
                (measure.MessageHeight > 0.0F OrElse measure.DetailHeight > 0.0F)

            If hasContent Then
                currentTop += contentTopGap
            End If

            Dim contentHostLeft As Single = hostRect.Left
            Dim contentHostRight As Single = hostRect.Right

            Dim reservedBelowContentHeight As Single = 0.0F

            If request.ShowCheckBox Then
                reservedBelowContentHeight +=
        (metrics.CheckBoxTopGap * dpi) +
        (metrics.CheckBoxHeight * dpi)
            End If

            If measure.ButtonsBlockHeight > 0.0F Then
                reservedBelowContentHeight +=
        measure.EffectiveButtonsTopGap +
        measure.ButtonsBlockHeight +
        buttonsBottomGap
            End If

            Dim maxContentHostBottom As Single =
    Math.Max(currentTop, hostRect.Bottom - reservedBelowContentHeight)

            Dim contentHostBottom As Single =
    Math.Min(maxContentHostBottom, currentTop + measure.ContentViewportHeight)

            Dim contentHostRect As New SKRect(
    contentHostLeft,
    currentTop,
    contentHostRight,
    contentHostBottom)

            result.ContentScrollHostRect = PixelSnap.SnapRectHalf(contentHostRect)

            Dim actualViewportHeight As Single = Math.Max(0.0F, result.ContentScrollHostRect.Height)
            Dim actualMaxScrollOffsetY As Single = Math.Max(0.0F, measure.ContentNaturalHeight - actualViewportHeight)
            Dim actualHasScrollableContent As Boolean = (actualMaxScrollOffsetY > 0.5F)

            result.HasScrollableContent = actualHasScrollableContent
            result.MaxContentScrollOffsetY = actualMaxScrollOffsetY

            Dim textHostRect As SKRect = result.ContentScrollHostRect
            result.ContentScrollRailRect = SKRect.Empty

            If actualHasScrollableContent Then
                Dim railMetrics As ScrollRailPolicy.Metrics =
                    ScrollRailPolicy.Build(result.ContentScrollHostRect, dpi)

                textHostRect = railMetrics.ContentRect
                result.ContentScrollRailRect = railMetrics.RailRect
            End If

            Dim contentWidth As Single = Math.Max(0.0F, textHostRect.Width)
            Dim readableMaxWidth As Single = metrics.ContentMaxReadableWidth * dpi
            Dim textWidth As Single = Math.Min(contentWidth, readableMaxWidth)

            Dim textLeft As Single = textHostRect.Left
            Dim textRight As Single = textLeft + textWidth

            result.TextLeft = textLeft
            result.TextRight = textRight

            Dim contentSectionTop As Single = currentTop
            Dim messageTop As Single = contentSectionTop

            If measure.MessageHeight > 0.0F Then
                messageTop += contentTopPad
                messageTop += (metrics.MessageTopInset * dpi)
            End If

            If measure.MessageHeight > 0.0F Then
                result.MessageRect = New SKRect(
                    textLeft,
                    messageTop,
                    textRight,
                    messageTop + measure.MessageHeight)

                currentTop = result.MessageRect.Bottom
            Else
                result.MessageRect = SKRect.Empty
                currentTop = messageTop
            End If

            If measure.DetailHeight > 0.0F Then
                If measure.MessageHeight > 0.0F Then
                    currentTop += detailTopGap
                End If

                result.DetailRect = New SKRect(
                    textLeft,
                    currentTop,
                    textRight,
                    currentTop + measure.DetailHeight)

                currentTop = result.DetailRect.Bottom
            Else
                result.DetailRect = SKRect.Empty
            End If

            If hasContent Then
                If measure.DetailHeight > 0.0F OrElse measure.MessageLineCount > 1 Then
                    currentTop += contentBottomGap
                Else
                    currentTop += Math.Min(contentBottomGap, 3.0F * dpi)
                End If
            End If

            result.ContentRect = New SKRect(
                textLeft,
                contentSectionTop,
                textRight,
                Math.Max(contentSectionTop, currentTop))

            result.ContentViewportRect = PixelSnap.SnapRectHalf(
                New SKRect(
                    textLeft,
                    textHostRect.Top,
                    textRight,
                    textHostRect.Bottom))

            currentTop = result.ContentScrollHostRect.Bottom

            If request.ShowCheckBox Then
                currentTop += metrics.CheckBoxTopGap * dpi

                Dim rowH As Single = metrics.CheckBoxHeight * dpi
                Dim boxSize As Single = metrics.CheckBoxBoxSize * dpi
                Dim textGap As Single = metrics.CheckBoxTextGap * dpi

                result.CheckBoxRowRect = New SKRect(
                    contentHostLeft,
                    currentTop,
                    contentHostRight,
                    currentTop + rowH)

                Dim boxTop As Single =
                    result.CheckBoxRowRect.MidY - (boxSize * 0.5F)

                result.CheckBoxBoxRect = New SKRect(
                    contentHostLeft,
                    boxTop,
                    contentHostLeft + boxSize,
                    boxTop + boxSize)

                result.CheckBoxTextRect = New SKRect(
                    result.CheckBoxBoxRect.Right + textGap,
                    result.CheckBoxRowRect.Top,
                    contentHostRight,
                    result.CheckBoxRowRect.Bottom)

                currentTop = result.CheckBoxRowRect.Bottom
            Else
                result.CheckBoxRowRect = SKRect.Empty
                result.CheckBoxBoxRect = SKRect.Empty
                result.CheckBoxTextRect = SKRect.Empty
            End If

            Dim buttons As IReadOnlyList(Of MASButtonSpec) =
                GetSafeButtons(request)

            If measure.ButtonWidths.Count > 0 AndAlso
               buttons IsNot Nothing AndAlso
               buttons.Count = measure.ButtonWidths.Count Then

                currentTop += measure.EffectiveButtonsTopGap

                Dim rows As List(Of List(Of Integer)) =
                    MessageBoxButtonLayoutPlanner.ArrangeRowsByWidths(
                        measure.ButtonWidths,
                        contentHostRight - contentHostLeft,
                        buttonGap)

                Dim blockTop As Single = currentTop
                Dim rowTop As Single = blockTop

                For Each row As List(Of Integer) In rows
                    Dim x As Single = contentHostLeft

                    For i As Integer = 0 To row.Count - 1
                        Dim idx As Integer = row(i)
                        Dim bw As Single = measure.ButtonWidths(idx)

                        result.ButtonRects.Add(New SKRect(
                            x,
                            rowTop,
                            x + bw,
                            rowTop + buttonHeight))

                        x += bw + buttonGap
                    Next

                    rowTop += buttonHeight + buttonsRowGap
                Next

                Dim blockBottom As Single = rowTop - buttonsRowGap

                If rows.Count = 0 Then
                    blockBottom = blockTop
                End If

                result.ButtonsBlockRect =
                    New SKRect(contentHostLeft, blockTop, contentHostRight, blockBottom)

                result.ButtonsRowRect =
                    If(result.ButtonRects.Count > 0,
                       New SKRect(contentHostLeft, blockTop, contentHostRight, blockTop + buttonHeight),
                       SKRect.Empty)

                currentTop = blockBottom + buttonsBottomGap
            Else
                result.ButtonsRowRect = SKRect.Empty
                result.ButtonsBlockRect = SKRect.Empty
            End If



            Return result
        End Function

        Friend Shared Function GetSafeButtons(request As MessageBoxRequest) As IReadOnlyList(Of MASButtonSpec)
            If request IsNot Nothing AndAlso
               request.Buttons IsNot Nothing AndAlso
               request.Buttons.Count > 0 Then

                Return request.Buttons
            End If

            Return MessageBoxPresets.OK()
        End Function

    End Class

End Namespace