Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports Nexamas.UI.Components
Imports Nexamas.UI.Controls
Imports Nexamas.UI.Rendering
Imports SkiaSharp

Namespace Nexamas.UI.Application

    ''' <summary>
    ''' Internal translation from the Friend-owned MASSelectionPanelOptions contract to the existing
    ''' PanelShell float implementation. This is the official
    ''' high-level API builder that keeps PanelShell geometry and generated controls internal.
    ''' </summary>
    Friend NotInheritable Class MASSelectionPanelRequestBuilder

        Friend Const ApplyCommandId As String = "apply"

        Private Const RecommendedCommandId As String = "recommended"
        Private Const SelectAllCommandId As String = "select-all"
        Private Const UnselectCommandId As String = "unselect"
        Private Const CancelCommandId As String = "cancel"

        Private Sub New()
        End Sub

        Friend Shared Function Build(options As MASSelectionPanelOptions,
                                     completed As Action(Of MASSelectionPanelResult)) As MASApplicationPanelShellOptions

            If options Is Nothing Then Throw New ArgumentNullException(NameOf(options))

            Dim items As IReadOnlyList(Of MASSelectionPanelItem) = options.CloneItems()
            Dim state As New MASSelectionPanelState(options.SelectionMode)

            Dim shellOptions As MASPanelShellDialogOptions = MASPanelShellDialogOptions.SelectionDialog(SafeText(options.Title, "Selection"))
            shellOptions.Subtitle = SafeText(options.Subtitle, String.Empty)
            shellOptions.ShowCloseButton = options.ShowCloseButton
            shellOptions.AllowHeaderDrag = options.AllowHeaderDrag
            shellOptions.SurfaceVisualStyleKey = MASSurfaceVisualStyleIds.PlatformDialogFrame
            shellOptions.SurfaceMaterialKey = MASSurfaceTreatmentMaterialKeyNormalizer.NormalizeForStorage(options.SurfaceMaterial)

            Dim panelOptions As New MASApplicationPanelShellOptions() With {
                .Name = SafeName(options.Name, "MASSelectionPanel"),
                .ShellOptions = shellOptions,
                .CloseOnEscape = options.CloseOnEscape,
                .CloseOnOutsidePointer = options.CloseOnOutsidePointer,
                .SwallowOutsidePointer = options.SwallowOutsidePointer,
                .UserState = options.UserState,
                .ContentBuilder = Sub(host As MASPanelShellContentHost, contentBounds As SKRect)
                                      BuildContent(host, contentBounds, options, items, state)
                                  End Sub,
                .Completed = Sub(result As MASApplicationPanelResult)
                                 If completed IsNot Nothing Then
                                     completed.Invoke(state.ToResult(result, options))
                                 End If
                             End Sub
            }

            MASApplicationPanelShellLayoutPolicy.ApplySelectionPanelSizePreset(panelOptions, options.SizePreset)
            MASApplicationPanelShellLayoutPolicy.ConfigureSelectionPanelChrome(panelOptions)
            ConfigureToolbar(panelOptions, options, state)
            ConfigureFooter(panelOptions, options)

            Return panelOptions

        End Function

        Private Shared Sub ConfigureToolbar(panelOptions As MASApplicationPanelShellOptions,
                                            options As MASSelectionPanelOptions,
                                            state As MASSelectionPanelState)

            If panelOptions Is Nothing OrElse options Is Nothing OrElse state Is Nothing Then Return

            Dim hasToolbar As Boolean = options.ShowRecommendedAction OrElse
                                    options.ShowSelectAllAction OrElse
                                    options.ShowUnselectAction

            MASApplicationPanelShellLayoutPolicy.ConfigureSelectionPanelToolbar(panelOptions, hasToolbar)

            If Not hasToolbar Then Return

            If options.ShowRecommendedAction Then
                Dim recommendedAction As MASApplicationPanelAction =
                    panelOptions.Toolbar.AddSecondary(SafeText(options.RecommendedActionText, "Recommended"), RecommendedCommandId)

                recommendedAction.Callback = Sub(result As MASApplicationPanelResult)
                                                 state.SelectRecommended()
                                             End Sub
            End If

            If options.ShowSelectAllAction AndAlso options.SelectionMode = MASSelectionPanelSelectionMode.Multiple Then
                Dim selectAllAction As MASApplicationPanelAction =
                    panelOptions.Toolbar.AddSecondary(SafeText(options.SelectAllActionText, "Select All"), SelectAllCommandId)

                selectAllAction.Callback = Sub(result As MASApplicationPanelResult)
                                               state.SelectAll()
                                           End Sub
            End If

            If options.ShowUnselectAction Then
                Dim unselectAction As MASApplicationPanelAction =
                    panelOptions.Toolbar.AddSecondary(SafeText(options.UnselectActionText, "Unselect"), UnselectCommandId)

                unselectAction.Callback = Sub(result As MASApplicationPanelResult)
                                              state.UnselectAll()
                                          End Sub
            End If

        End Sub

        Private Shared Sub ConfigureFooter(panelOptions As MASApplicationPanelShellOptions,
                                           options As MASSelectionPanelOptions)

            If panelOptions Is Nothing OrElse options Is Nothing Then Return

            MASApplicationPanelShellLayoutPolicy.ConfigureSelectionPanelFooter(panelOptions)
            panelOptions.Footer.Clear()

            Dim primaryAction As MASApplicationPanelAction =
                panelOptions.Footer.AddPrimary(SafeText(options.PrimaryButtonText, "OK"), ApplyCommandId)

            primaryAction.IsDefaultButton = True

            If options.ShowCancelAction Then
                panelOptions.Footer.AddSecondary(SafeText(options.CancelButtonText, "Cancel"), CancelCommandId)
            End If

        End Sub

        Private Shared Sub BuildContent(host As MASPanelShellContentHost,
                                        contentBounds As SKRect,
                                        options As MASSelectionPanelOptions,
                                        items As IReadOnlyList(Of MASSelectionPanelItem),
                                        state As MASSelectionPanelState)

            If host Is Nothing OrElse options Is Nothing OrElse items Is Nothing OrElse state Is Nothing Then Return

            Dim width As Single = Math.Max(1.0F, contentBounds.Width)
            Dim height As Single = Math.Max(1.0F, contentBounds.Height)
            Dim leftPad As Single = MASApplicationPanelShellLayoutPolicy.SelectionPanelContentLeftPad()
            Dim top As Single = 0.0F

            Dim descriptionText As String = SafeText(options.DescriptionText, String.Empty)
            Dim hasDescription As Boolean = descriptionText.Trim().Length > 0
            Dim descriptionHeight As Single = MASApplicationPanelShellLayoutPolicy.SelectionPanelDescriptionHeight(width, hasDescription)
            Dim countHeight As Single = MASApplicationPanelShellLayoutPolicy.SelectionPanelCountRowHeight()
            Dim scrollTopGap As Single = MASApplicationPanelShellLayoutPolicy.SelectionPanelScrollTopGap()

            If hasDescription Then
                Dim description As New MASLabel(descriptionText) With {
                    .Name = "MASSelectionPanelDescription",
                    .LabelVariant = MASLabelVariant.Body,
                    .TextRole = MASLabelTextRole.Secondary,
                    .MultiLine = True,
                    .WordWrap = True,
                    .MaxLines = 2,
                    .VerticalAlignment = MASVerticalAlignment.Top
                }

                host.AddFixed(description, New SKRect(leftPad, top, width - leftPad, top + descriptionHeight))
                top += descriptionHeight + MASApplicationPanelShellLayoutPolicy.SelectionPanelDescriptionBottomGap()
            End If

            Dim selectionCountLabel As New MASLabel(String.Empty) With {
                .Name = "MASSelectionPanelSelectionCount",
                .LabelVariant = MASLabelVariant.Caption,
                .TextRole = MASLabelTextRole.Secondary,
                .VerticalAlignment = MASVerticalAlignment.Center
            }

            host.AddFixed(selectionCountLabel, New SKRect(leftPad, top, width - leftPad, top + countHeight))
            top += countHeight + scrollTopGap

            state.Reset(
                countLabel:=selectionCountLabel,
                summaryFormat:=options.SelectionSummaryFormat)

            Dim scrollArea As New SKRect(
                0.0F,
                top,
                width,
                Math.Max(top + 1.0F, height))

            Dim scrollContentArea As SKRect = host.SetScrollableArea(scrollArea)
            Dim scrollContentWidth As Single = Math.Max(1.0F, scrollContentArea.Width)

            Dim columns As Integer = ResolveColumnCount(options, items.Count)
            Dim columnGap As Single = MASApplicationPanelShellLayoutPolicy.SelectionPanelColumnGap()
            Dim rowHeight As Single = MASApplicationPanelShellLayoutPolicy.SelectionPanelRowHeight()
            Dim rowGap As Single = MASApplicationPanelShellLayoutPolicy.SelectionPanelRowGap()
            Dim estimatedScrollRailWidth As Single = MASApplicationPanelShellLayoutPolicy.SelectionPanelEstimatedScrollRailWidth()
            Dim scrollLeftPad As Single = MASApplicationPanelShellLayoutPolicy.SelectionPanelContentLeftPad()
            Dim gridWidth As Single = Math.Max(1.0F, scrollContentWidth - estimatedScrollRailWidth)
            Dim columnWidth As Single = Math.Max(MASApplicationPanelShellLayoutPolicy.SelectionPanelMinimumColumnWidth(), (gridWidth - scrollLeftPad - (columnGap * CSng(columns - 1))) / CSng(columns))
            Dim visibleIndex As Integer = 0

            For i As Integer = 0 To items.Count - 1
                Dim item As MASSelectionPanelItem = items(i)
                If item Is Nothing Then Continue For
                If Not item.Visible Then Continue For

                Dim box As New MASCheckBox() With {
                    .Name = BuildItemCheckName(item.Key, visibleIndex),
                    .Text = SafeText(item.Text, "Selection item"),
                    .Checked = item.Selected,
                    .Visible = True,
                    .Enabled = item.Enabled
                }

                state.Add(box, item)

                Dim column As Integer = visibleIndex Mod columns
                Dim row As Integer = visibleIndex \ columns
                Dim x As Single = scrollLeftPad + (CSng(column) * (columnWidth + columnGap))
                Dim y As Single = CSng(row) * (rowHeight + rowGap)

                host.AddScrollable(box, New SKRect(x, y, x + columnWidth, y + rowHeight))
                visibleIndex += 1
            Next

            EnforceInitialSingleSelection(state, options.SelectionMode)
            state.RefreshCount()

        End Sub

        Private Shared Sub EnforceInitialSingleSelection(state As MASSelectionPanelState,
                                                         selectionMode As MASSelectionPanelSelectionMode)

            If state Is Nothing Then Return
            If selectionMode <> MASSelectionPanelSelectionMode.[Single] Then Return

            state.NormalizeSingleSelection()

        End Sub

        Private Shared Function ResolveColumnCount(options As MASSelectionPanelOptions,
                                                   itemCount As Integer) As Integer

            If options Is Nothing Then Return 1

            Select Case options.LayoutKind
                Case MASSelectionPanelLayoutKind.List
                    Return 1

                Case MASSelectionPanelLayoutKind.Grid
                    Return If(itemCount <= MASApplicationPanelShellLayoutPolicy.SelectionPanelGridThresholdItemCount(), 1, MASApplicationPanelShellLayoutPolicy.SelectionPanelGridColumnCount())

                Case Else
                    Return If(itemCount <= MASApplicationPanelShellLayoutPolicy.SelectionPanelGridThresholdItemCount(), 1, MASApplicationPanelShellLayoutPolicy.SelectionPanelGridColumnCount())
            End Select

        End Function

        Private Shared Function BuildItemCheckName(key As String,
                                                   index As Integer) As String

            Dim safeKey As String = If(key, String.Empty).Trim()
            If safeKey.Length = 0 Then safeKey = "Item" & index.ToString()

            Dim chars As Char() = safeKey.ToCharArray()

            For i As Integer = 0 To chars.Length - 1
                If Not Char.IsLetterOrDigit(chars(i)) Then
                    chars(i) = "_"c
                End If
            Next

            Return "MASSelectionPanelItem_" & New String(chars)

        End Function

        Private Shared Function SafeName(value As String,
                                         fallback As String) As String

            Dim safe As String = If(value, String.Empty).Trim()
            If safe.Length = 0 Then Return fallback
            Return safe

        End Function

        Private Shared Function SafeText(value As String,
                                         fallback As String) As String

            Dim safe As String = If(value, String.Empty)
            If safe.Trim().Length = 0 Then Return If(fallback, String.Empty)
            Return safe

        End Function

    End Class

End Namespace
