Option Strict On
Option Explicit On

Imports System
Imports Nexamas.UI.Components
Imports Nexamas.UI.FileSystem
Imports Nexamas.UI.FloatRuntime
Imports Nexamas.UI.Host
Imports Nexamas.UI.Layout
Imports Nexamas.UI.Theming
Imports SkiaSharp

Namespace Nexamas.UI.FloatRuntime

    ''' <summary>
    ''' Builds MASFloatRequest instances for MASFilePickerControl.
    ''' </summary>
    ''' <remarks>
    ''' This builder is lifecycle-neutral.
    '''
    ''' It does not call MASFloatRuntime.Show.
    ''' It does not close floats.
    ''' It does not keep current/global state.
    ''' It does not depend on legacy Overlay.
    ''' It does not create wrappers or launchers.
    '''
    ''' Responsibilities:
    ''' - Create MASFilePickerControl.
    ''' - Host it inside MASFloatControlsContent.
    ''' - Bridge FilePicker Accepted/Cancelled events to IMASFloatSession.
    ''' - Build a MASFloatRequest using MASFloatKeys.FilePicker.
    '''
    ''' Result delivery:
    ''' - MASFilePickerResult is passed to IMASFloatSession.Accept(result).
    ''' - External consumers must read the final result from MASFloatRequest.OnClosed.
    ''' - OnClosed is delivered only after the Float reaches Closed by MASFloatRuntime.
    ''' </remarks>
    Friend NotInheritable Class MASFilePickerFloatRequestBuilder

        Private Const DefaultOwnerKeyPrefix As String = "MAS.FilePicker"

        Private Sub New()
        End Sub

#Region "Generic"

        Friend Shared Function CreateRequest(rootHost As MASSkiaRootHost,
                                             options As MASFilePickerOptions,
                                             ownerKey As String,
                                             Optional initialBoundsPx As SKRect = Nothing,
                                             Optional showPolicy As MASFloatShowPolicy = MASFloatShowPolicy.CloseSameOwnerThenShow,
                                             Optional shield As MASFloatShieldOptions = Nothing,
                                             Optional onOpened As Action(Of MASFloatHandle) = Nothing,
                                             Optional onClosed As Action(Of MASFloatClosedEventArgs) = Nothing) As MASFloatRequest

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

            If String.IsNullOrWhiteSpace(ownerKey) Then
                Throw New ArgumentException("FilePicker Float requires a non-empty ownerKey.", NameOf(ownerKey))
            End If

            Dim safeOptions As MASFilePickerOptions = MASFilePickerOptions.SafeClone(options)

            Dim picker As New MASFilePickerControl(safeOptions)

            Dim content As New MASFloatControlsContent(
                name:="MASFilePickerFloatContent",
                themeHost:=rootHost.ThemeHostCore,
                invalidate:=AddressOf rootHost.RequestInvalidate
            )

            content.Add(picker)

            content.AcceptsPointerInput = True
            content.AcceptsKeyboardInput = True
            content.AcceptsTextInput = True

            content.CloseOnEscape = True
            content.CloseOnOutsidePointer = False
            content.SwallowOutsidePointer = True

            Dim openedHandle As MASFloatHandle = Nothing

            WirePickerToSession(
                rootHost:=rootHost,
                ownerKey:=ownerKey.Trim(),
                picker:=picker,
                content:=content,
                getParentHandle:=Function()
                                     Return openedHandle
                                 End Function)

            Dim request As New MASFloatRequest() With {
                .FloatKey = MASFloatKeys.FilePicker,
                .OwnerKey = ownerKey.Trim(),
                .Content = content,
                .ShowPolicy = showPolicy,
                .Shield = If(shield, MASFloatShieldOptions.None),
                .OnOpened = Sub(handle As MASFloatHandle)
                                openedHandle = handle

                                If onOpened IsNot Nothing Then
                                    onOpened.Invoke(handle)
                                End If
                            End Sub,
                .OnClosed = onClosed
            }

            Dim availablePx As SKRect = rootHost.GetSurfaceViewportPx()

            If IsUsableRect(availablePx) Then
                request.AvailableBoundsPx = availablePx
            End If

            If IsUsableRect(initialBoundsPx) Then
                request.InitialBoundsPx = initialBoundsPx
            Else
                request.InitialBoundsPx = CreateDefaultCenteredBoundsPx(rootHost, availablePx, picker)
            End If

            Return request
        End Function

#End Region

#Region "Open file"

        Friend Shared Function OpenFile(rootHost As MASSkiaRootHost,
                                        ownerKey As String,
                                        Optional title As String = "Open File",
                                        Optional initialPath As String = "",
                                        Optional filter As String = "*.*",
                                        Optional showHidden As Boolean = False,
                                        Optional allowMultiSelect As Boolean = False,
                                        Optional footerText As String = "",
                                        Optional primaryButtonText As String = "",
                                        Optional onOpened As Action(Of MASFloatHandle) = Nothing,
                                        Optional onClosed As Action(Of MASFloatClosedEventArgs) = Nothing) As MASFloatRequest

            Return CreateRequest(
                rootHost:=rootHost,
                ownerKey:=ownerKey,
                options:=MASFilePickerOptions.OpenFile(
                    title:=title,
                    initialPath:=initialPath,
                    filter:=filter,
                    showHidden:=showHidden,
                    allowMultiSelect:=allowMultiSelect,
                    footerText:=footerText,
                    primaryButtonText:=primaryButtonText
                ),
                showPolicy:=MASFloatShowPolicy.CloseSameOwnerThenShow,
                shield:=MASFloatShieldOptions.None,
                onOpened:=onOpened,
                onClosed:=onClosed
            )
        End Function

#End Region

#Region "Save file"

        Friend Shared Function SaveFile(rootHost As MASSkiaRootHost,
                                        ownerKey As String,
                                        Optional title As String = "Save File",
                                        Optional initialPath As String = "",
                                        Optional filter As String = "*.*",
                                        Optional fileName As String = "",
                                        Optional confirmOverwrite As Boolean = True,
                                        Optional showHidden As Boolean = False,
                                        Optional primaryButtonText As String = "",
                                        Optional footerText As String = "",
                                        Optional onOpened As Action(Of MASFloatHandle) = Nothing,
                                        Optional onClosed As Action(Of MASFloatClosedEventArgs) = Nothing) As MASFloatRequest

            Return CreateRequest(
                rootHost:=rootHost,
                ownerKey:=ownerKey,
                options:=MASFilePickerOptions.SaveFile(
                    title:=title,
                    initialPath:=initialPath,
                    filter:=filter,
                    fileName:=fileName,
                    confirmOverwrite:=confirmOverwrite,
                    showHidden:=showHidden,
                    primaryButtonText:=primaryButtonText,
                    footerText:=footerText
                ),
                showPolicy:=MASFloatShowPolicy.CloseSameOwnerThenShow,
                shield:=MASFloatShieldOptions.None,
                onOpened:=onOpened,
                onClosed:=onClosed
            )
        End Function

#End Region

#Region "Folder picker"

        Friend Shared Function PickFolder(rootHost As MASSkiaRootHost,
                                          ownerKey As String,
                                          Optional title As String = "Select Folder",
                                          Optional initialPath As String = "",
                                          Optional showHidden As Boolean = False,
                                          Optional primaryButtonText As String = "",
                                          Optional acceptCurrentPathWhenNoSelection As Boolean = False,
                                          Optional footerText As String = "",
                                          Optional onOpened As Action(Of MASFloatHandle) = Nothing,
                                          Optional onClosed As Action(Of MASFloatClosedEventArgs) = Nothing) As MASFloatRequest

            Return CreateRequest(
                rootHost:=rootHost,
                ownerKey:=ownerKey,
                options:=MASFilePickerOptions.FolderPicker(
                    title:=title,
                    initialPath:=initialPath,
                    showHidden:=showHidden,
                    primaryButtonText:=primaryButtonText,
                    acceptCurrentPathWhenNoSelection:=acceptCurrentPathWhenNoSelection,
                    footerText:=footerText
                ),
                showPolicy:=MASFloatShowPolicy.CloseSameOwnerThenShow,
                shield:=MASFloatShieldOptions.None,
                onOpened:=onOpened,
                onClosed:=onClosed
            )
        End Function

#End Region

#Region "Targets picker"

        Friend Shared Function PickTargets(rootHost As MASSkiaRootHost,
                                           ownerKey As String,
                                           targetKinds As MASFileSystemSelectionKinds,
                                           Optional title As String = "Select Targets",
                                           Optional initialPath As String = "",
                                           Optional showHidden As Boolean = False,
                                           Optional allowMultiSelect As Boolean = False,
                                           Optional primaryButtonText As String = "",
                                           Optional acceptCurrentPathWhenNoSelection As Boolean = False,
                                           Optional footerText As String = "",
                                           Optional onOpened As Action(Of MASFloatHandle) = Nothing,
                                           Optional onClosed As Action(Of MASFloatClosedEventArgs) = Nothing) As MASFloatRequest

            Return CreateRequest(
                rootHost:=rootHost,
                ownerKey:=ownerKey,
                options:=MASFilePickerOptions.TargetsPicker(
                    targetKinds:=targetKinds,
                    title:=title,
                    initialPath:=initialPath,
                    showHidden:=showHidden,
                    allowMultiSelect:=allowMultiSelect,
                    primaryButtonText:=primaryButtonText,
                    acceptCurrentPathWhenNoSelection:=acceptCurrentPathWhenNoSelection,
                    footerText:=footerText
                ),
                showPolicy:=MASFloatShowPolicy.CloseSameOwnerThenShow,
                shield:=MASFloatShieldOptions.None,
                onOpened:=onOpened,
                onClosed:=onClosed
            )
        End Function

        Friend Shared Function PickDrive(rootHost As MASSkiaRootHost,
                                         ownerKey As String,
                                         Optional title As String = "Select Drive",
                                         Optional initialPath As String = "",
                                         Optional showHidden As Boolean = False,
                                         Optional allowMultiSelect As Boolean = False,
                                         Optional primaryButtonText As String = "",
                                         Optional footerText As String = "",
                                         Optional onOpened As Action(Of MASFloatHandle) = Nothing,
                                         Optional onClosed As Action(Of MASFloatClosedEventArgs) = Nothing) As MASFloatRequest

            Return CreateRequest(
                rootHost:=rootHost,
                ownerKey:=ownerKey,
                options:=MASFilePickerOptions.DrivePicker(
                    title:=title,
                    initialPath:=initialPath,
                    showHidden:=showHidden,
                    allowMultiSelect:=allowMultiSelect,
                    primaryButtonText:=primaryButtonText,
                    footerText:=footerText
                ),
                showPolicy:=MASFloatShowPolicy.CloseSameOwnerThenShow,
                shield:=MASFloatShieldOptions.None,
                onOpened:=onOpened,
                onClosed:=onClosed
            )
        End Function

        Friend Shared Function PickFolders(rootHost As MASSkiaRootHost,
                                           ownerKey As String,
                                           Optional title As String = "Select Folders",
                                           Optional initialPath As String = "",
                                           Optional showHidden As Boolean = False,
                                           Optional primaryButtonText As String = "",
                                           Optional acceptCurrentPathWhenNoSelection As Boolean = False,
                                           Optional footerText As String = "",
                                           Optional onOpened As Action(Of MASFloatHandle) = Nothing,
                                           Optional onClosed As Action(Of MASFloatClosedEventArgs) = Nothing) As MASFloatRequest

            Return CreateRequest(
                rootHost:=rootHost,
                ownerKey:=ownerKey,
                options:=MASFilePickerOptions.MultiFolderPicker(
                    title:=title,
                    initialPath:=initialPath,
                    showHidden:=showHidden,
                    primaryButtonText:=primaryButtonText,
                    acceptCurrentPathWhenNoSelection:=acceptCurrentPathWhenNoSelection,
                    footerText:=footerText
                ),
                showPolicy:=MASFloatShowPolicy.CloseSameOwnerThenShow,
                shield:=MASFloatShieldOptions.None,
                onOpened:=onOpened,
                onClosed:=onClosed
            )
        End Function

        Friend Shared Function PickFolderOrDrive(rootHost As MASSkiaRootHost,
                                                 ownerKey As String,
                                                 Optional title As String = "Select Targets",
                                                 Optional initialPath As String = "",
                                                 Optional showHidden As Boolean = False,
                                                 Optional allowMultiSelect As Boolean = True,
                                                 Optional primaryButtonText As String = "",
                                                 Optional acceptCurrentPathWhenNoSelection As Boolean = False,
                                                 Optional footerText As String = "",
                                                 Optional onOpened As Action(Of MASFloatHandle) = Nothing,
                                                 Optional onClosed As Action(Of MASFloatClosedEventArgs) = Nothing) As MASFloatRequest

            Return CreateRequest(
                rootHost:=rootHost,
                ownerKey:=ownerKey,
                options:=MASFilePickerOptions.FolderAndDrivePicker(
                    title:=title,
                    initialPath:=initialPath,
                    showHidden:=showHidden,
                    allowMultiSelect:=allowMultiSelect,
                    primaryButtonText:=primaryButtonText,
                    acceptCurrentPathWhenNoSelection:=acceptCurrentPathWhenNoSelection,
                    footerText:=footerText
                ),
                showPolicy:=MASFloatShowPolicy.CloseSameOwnerThenShow,
                shield:=MASFloatShieldOptions.None,
                onOpened:=onOpened,
                onClosed:=onClosed
            )
        End Function

#End Region

#Region "Bridge"

        Private Shared Sub WirePickerToSession(rootHost As MASSkiaRootHost,
                                               ownerKey As String,
                                               picker As MASFilePickerControl,
                                               content As MASFloatControlsContent,
                                               getParentHandle As Func(Of MASFloatHandle))

            If rootHost Is Nothing Then Throw New ArgumentNullException(NameOf(rootHost))
            If picker Is Nothing Then Throw New ArgumentNullException(NameOf(picker))
            If content Is Nothing Then Throw New ArgumentNullException(NameOf(content))

            AddHandler picker.Accepted,
                Sub(sender As Object, e As MASFilePickerResultEventArgs)
                    If e Is Nothing OrElse e.Result Is Nothing Then Return

                    Dim session As IMASFloatSession = content.Session
                    If session Is Nothing Then Return

                    session.Accept(e.Result)
                End Sub

            AddHandler picker.Cancelled,
                Sub(sender As Object, e As EventArgs)
                    Dim session As IMASFloatSession = content.Session
                    If session Is Nothing Then Return

                    session.Cancel()
                End Sub

            AddHandler picker.ItemContextMenuRequested,
                Sub(sender As Object, e As MASFilePickerItemContextMenuRequestedEventArgs)
                    If e Is Nothing OrElse e.Entry Is Nothing Then Return

                    Dim parentHandle As MASFloatHandle = ResolveParentHandle(getParentHandle)
                    Dim pendingRenameEntry As FileSystemEntry = Nothing

                    Dim request As MASFloatRequest =
                        MASFilePickerContextMenuFloatRequestBuilder.CreateItemRequest(
                            rootHost:=rootHost,
                            ownerKey:=ownerKey & ".ItemContextMenu",
                            entry:=e.Entry,
                            anchorRectPx:=e.AnchorRectPx,
                            parentHandle:=parentHandle,
                            onCommand:=Sub(commandId As String, entry As FileSystemEntry)
                                           Dim id As String = If(commandId, String.Empty).Trim()

                                           If String.Equals(id, MASFilePickerContextMenuCommands.Rename, StringComparison.Ordinal) Then
                                               pendingRenameEntry = entry
                                               Return
                                           End If

                                           picker.ExecuteItemContextMenuCommand(id, entry)
                                       End Sub,
                            onClosed:=Sub(args As MASFloatClosedEventArgs)
                                          Dim entryToRename As FileSystemEntry = pendingRenameEntry
                                          pendingRenameEntry = Nothing

                                          If entryToRename IsNot Nothing Then
                                              picker.BeginInlineRenameEntry(entryToRename)
                                          End If
                                      End Sub)

                    If request IsNot Nothing Then
                        ShowCompatibility(rootHost, request)
                    End If
                End Sub

            AddHandler picker.EmptySpaceContextMenuRequested,
                Sub(sender As Object, e As MASFilePickerEmptySpaceContextMenuRequestedEventArgs)
                    If e Is Nothing Then Return

                    Dim parentHandle As MASFloatHandle = ResolveParentHandle(getParentHandle)

                    Dim request As MASFloatRequest =
                        MASFilePickerContextMenuFloatRequestBuilder.CreateBackgroundRequest(
                            rootHost:=rootHost,
                            ownerKey:=ownerKey & ".FolderContextMenu",
                            currentFolderPath:=picker.CurrentPath,
                            anchorRectPx:=e.AnchorRectPx,
                            parentHandle:=parentHandle,
                            onCommand:=Sub(commandId As String)
                                           picker.ExecuteBackgroundContextMenuCommand(commandId)
                                       End Sub)

                    If request IsNot Nothing Then
                        ShowCompatibility(rootHost, request)
                    End If
                End Sub

            AddHandler picker.InlineRenameFailed,
                Sub(sender As Object, e As MASFilePickerInlineRenameFailedEventArgs)
                    If e Is Nothing Then Return

                    ShowInlineRenameWarning(
                        rootHost:=rootHost,
                        ownerKey:=ownerKey & ".Rename.Warning",
                        message:=If(String.IsNullOrWhiteSpace(e.Message), "Rename failed.", e.Message),
                        parentHandle:=ResolveParentHandle(getParentHandle))
                End Sub
        End Sub

        Private Shared Function ResolveParentHandle(getParentHandle As Func(Of MASFloatHandle)) As MASFloatHandle
            If getParentHandle Is Nothing Then Return Nothing

            Try
                Return getParentHandle.Invoke()
            Catch masCaughtException1 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowFileSystemBoundary(masCaughtException1)
                Return Nothing
            End Try
        End Function

        Private Shared Sub ShowInlineRenameWarning(rootHost As MASSkiaRootHost,
                                                   ownerKey As String,
                                                   message As String,
                                                   parentHandle As MASFloatHandle)
            If rootHost Is Nothing Then Return

            Dim safeMessage As String = If(String.IsNullOrWhiteSpace(message), "Rename failed.", message)

            If parentHandle Is Nothing Then
                rootHost.ShowWarningMessageBox(
                    ownerKey:=ownerKey,
                    message:=safeMessage,
                    title:="Rename")
                Return
            End If

            Dim request As MASFloatRequest =
                rootHost.CreateMessageBoxFloatRequest(
                    ownerKey:=ownerKey,
                    messageBox:=MessageBoxRequestFactory.Warning(
                        message:=safeMessage,
                        title:="Rename"),
                    showPolicy:=MASFloatShowPolicy.CloseSameOwnerThenShow)

            If request Is Nothing Then Return

            request.ParentHandle = parentHandle
            request.RequestedScope = MASFloatScope.ParentFloat
            request.RequestedSlot = MASFloatSlot.Popup
            request.RequestedLayer = MASFloatLayerLevel.SystemCritical
            request.Shield = MASFloatShieldOptions.None

            ShowCompatibility(rootHost, request)
        End Sub

#End Region

#Region "Geometry"

        Private Shared Function CreateDefaultCenteredBoundsPx(rootHost As MASSkiaRootHost,
                                                                availableBoundsPx As SKRect,
                                                                surface As IMASFloatSurfaceSizeContract) As SKRect
            If rootHost Is Nothing Then Return SKRect.Empty

            If Not IsUsableRect(availableBoundsPx) Then
                availableBoundsPx = rootHost.GetSurfaceViewportPx()
            End If

            If Not IsUsableRect(availableBoundsPx) Then
                Return SKRect.Empty
            End If

            Dim dpi As Single = Math.Max(0.01F, rootHost.GetDpi())

            Dim resolvedBySizeLayout As SKRect =
                MASFloatSurfaceSizeContractResolver.TryResolveCenteredBoundsPixel(
                    surface:=surface,
                    context:=CreateSizeContext(rootHost, availableBoundsPx, dpi),
                    availableBoundsPx:=availableBoundsPx,
                    dpiScale:=dpi,
                    intent:=MASSize.Default)

            If Not IsUsableRect(resolvedBySizeLayout) Then
                resolvedBySizeLayout =
                    MASFloatSurfaceSizeResolver.ResolveCenteredBoundsPixel(
                        kind:=MASFloatSurfaceKind.FilePicker,
                        availableBoundsPx:=availableBoundsPx,
                        dpiScale:=dpi,
                        intent:=MASSize.Default)
            End If

            If IsUsableRect(resolvedBySizeLayout) Then
                Return resolvedBySizeLayout
            End If

            Return availableBoundsPx
        End Function

        Private Shared Function CreateSizeContext(rootHost As MASSkiaRootHost,
                                                        availableBoundsPx As SKRect,
                                                        dpi As Single) As MASSizeContext
            Dim safeDpi As Single = Math.Max(0.01F, dpi)
            Dim availableLogical As New SKSize(
                MASLayoutDpiResolver.PixelToLogical(Math.Max(1.0F, availableBoundsPx.Width), safeDpi),
                MASLayoutDpiResolver.PixelToLogical(Math.Max(1.0F, availableBoundsPx.Height), safeDpi))
            Dim themeContext As MASThemeContext = ResolveContext(rootHost)

            If themeContext Is Nothing Then
                Return New MASSizeContext(availableLogical, MASRuntimeSizeProfileService.Current, safeDpi)
            End If

            Return New MASSizeContext(
                availableLogical,
                MASSizeLayoutIntegrationGateway.Create(themeContext, MASRuntimeSizeProfileService.Current, safeDpi))
        End Function

        Private Shared Function ResolveContext(rootHost As MASSkiaRootHost) As MASThemeContext
            If rootHost Is Nothing Then Return Nothing

            Try
                Return rootHost.ThemeHostCore.TryGetContext()
            Catch masCaughtExceptionResolveFloatContext As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtExceptionResolveFloatContext, "FloatRequestBuilder.ResolveContext")
            End Try

            Return Nothing
        End Function

        Private Shared Function IsUsableRect(rect As SKRect) As Boolean
            Return Not rect.IsEmpty AndAlso
                   rect.Width > 1.0F AndAlso
                   rect.Height > 1.0F AndAlso
                   Not Single.IsNaN(rect.Left) AndAlso
                   Not Single.IsNaN(rect.Top) AndAlso
                   Not Single.IsNaN(rect.Right) AndAlso
                   Not Single.IsNaN(rect.Bottom) AndAlso
                   Not Single.IsInfinity(rect.Left) AndAlso
                   Not Single.IsInfinity(rect.Top) AndAlso
                   Not Single.IsInfinity(rect.Right) AndAlso
                   Not Single.IsInfinity(rect.Bottom)
        End Function

#End Region


        Private Shared Function ShowCompatibility(rootHost As MASSkiaRootHost,
                                                  request As MASFloatRequest) As MASFloatHandle
            If rootHost Is Nothing OrElse request Is Nothing Then Return Nothing

            Dim result As MASFloatShowResult = rootHost.FloatRuntimeCore.ShowEx(request)

            If result IsNot Nothing AndAlso result.Status = MASFloatShowStatus.Rejected Then
                Throw New InvalidOperationException("Float show rejected: " & result.Reason)
            End If

            Return If(result Is Nothing, Nothing, result.Handle)
        End Function

    End Class

End Namespace