Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.Components.ListPopup
Imports Nexamas.UI.Components.ListRendering
Imports Nexamas.UI.Controls
Imports Nexamas.UI.DataBinding
Imports Nexamas.UI.FloatRuntime
Imports Nexamas.UI.General
Imports Nexamas.UI.SearchBoxSystem
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Components

    Partial Public NotInheritable Class MASSearchTextBox

#Region "Advanced SearchBox state"

        Private ReadOnly _advancedSearchState As New MASSearchBoxAdvancedState()
        Private _advancedDropDownService As DropDownService
        Private _advancedRuntimeServices As MASControlRuntimeServices
        Private _advancedBoundDataGrid As MASDataGrid
        Private _advancedSuggestionPopupOpen As Boolean

#End Region

#Region "Advanced SearchBox public API"

        ''' <summary>
        ''' Raised when the user or application commits the current effective search text.
        ''' Suggestions and recent searches remain owned by MASSearchTextBox; DataGrid projection remains owned by MASDataGrid/MASDataView.
        ''' </summary>
        Public Event SearchCommitted As EventHandler

        Public Property MaxRecentSearches As Integer
            Get
                Return _advancedSearchState.MaxRecentSearches
            End Get
            Set(value As Integer)
                _advancedSearchState.MaxRecentSearches = value
                InvalidateVisual()
            End Set
        End Property

        Public ReadOnly Property SuggestionCount As Integer
            Get
                Return _advancedSearchState.SuggestionCount
            End Get
        End Property

        Public ReadOnly Property RecentSearchCount As Integer
            Get
                Return _advancedSearchState.RecentSearchCount
            End Get
        End Property

        Public ReadOnly Property FilterTokenCount As Integer
            Get
                Return _advancedSearchState.FilterTokenCount
            End Get
        End Property

        Public ReadOnly Property EffectiveSearchText As String
            Get
                Return _advancedSearchState.BuildEffectiveSearchText(Me.Text)
            End Get
        End Property

        Public Function SetSuggestions(values As IEnumerable(Of String)) As MASSearchTextBox
            _advancedSearchState.SetSuggestions(values)
            InvalidateVisual()
            Return Me
        End Function

        Public Function AddSuggestion(value As String) As MASSearchTextBox
            If _advancedSearchState.AddSuggestion(value) Then InvalidateVisual()
            Return Me
        End Function

        Public Function ClearSuggestions() As MASSearchTextBox
            If _advancedSearchState.ClearSuggestions() Then InvalidateVisual()
            CloseAdvancedSuggestionPopup()
            Return Me
        End Function

        Public Function AddRecentSearch(value As String) As MASSearchTextBox
            If _advancedSearchState.AddRecentSearch(value) Then InvalidateVisual()
            Return Me
        End Function

        Public Function ClearRecentSearches() As MASSearchTextBox
            If _advancedSearchState.ClearRecentSearches() Then InvalidateVisual()
            Return Me
        End Function

        Public Function AddFilterToken(columnKey As String, valueText As String) As MASSearchTextBox
            If _advancedSearchState.AddColumnFilter(columnKey, valueText) Then
                ApplyAdvancedDataGridFilter()
                InvalidateVisual()
            End If
            Return Me
        End Function

        Public Function AddFilterExpression(expressionText As String) As MASSearchTextBox
            If _advancedSearchState.AddFilterExpression(expressionText) Then
                ApplyAdvancedDataGridFilter()
                InvalidateVisual()
            End If
            Return Me
        End Function

        Public Function ClearFilterTokens() As MASSearchTextBox
            If _advancedSearchState.ClearFilterTokens() Then
                ApplyAdvancedDataGridFilter()
                InvalidateVisual()
            End If
            Return Me
        End Function

        Public Function BindToDataGrid(grid As MASDataGrid) As MASSearchTextBox
            _advancedBoundDataGrid = grid
            ApplyAdvancedDataGridFilter()
            Return Me
        End Function

        Public Function UnbindDataGrid() As MASSearchTextBox
            _advancedBoundDataGrid = Nothing
            Return Me
        End Function

        Public Function CommitSearch() As MASSearchTextBox
            Dim committedText As String = MASSearchTextBoxPolicy.NormalizeSearchText(EffectiveSearchText)
            _advancedSearchState.AddRecentSearch(committedText)
            ApplyAdvancedDataGridFilter()
            CloseAdvancedSuggestionPopup()
            RaiseSearchCommittedSafe()
            InvalidateVisual()
            Return Me
        End Function

        Public Function AcceptSuggestion(index As Integer) As MASSearchTextBox
            Dim items As ReadOnlyCollection(Of String) = _advancedSearchState.BuildSuggestionPopupItems(Me.Text, MASSearchTextBoxPolicy.NormalizeSuggestionPopupLimit(20))
            If index < 0 OrElse index >= items.Count Then Return Me
            Me.Text = items(index)
            CommitSearch()
            Return Me
        End Function

#End Region

#Region "Advanced SearchBox internal binding route"

        Friend Function CreateSearchTextBindingTarget(Optional targetName As String = "SearchText") As MASViewModelBindingTarget
            Return MASViewModelBindingTarget.Text(
                If(targetName, "SearchText"),
                Sub(value As String)
                    Me.Text = MASSearchTextBoxPolicy.NormalizeSearchText(value)
                End Sub,
                Function()
                    Return Me.Text
                End Function)
        End Function

        Friend Function CreateAdvancedReadinessManifest() As MASSearchBoxAdvancedReadinessManifest
            Return _advancedSearchState.CreateReadinessManifest(
                hasDataGridBindingRoute:=True,
                hasViewModelBindingTargetRoute:=True,
                hasNoParallelSearchPath:=True)
        End Function

#End Region

#Region "Advanced SearchBox initialization and runtime services"

        Private Sub InitializeAdvancedSearchBox()
            _advancedDropDownService =
                New DropDownService(
                    hasOwnedFloat:=Function(handle As MASFloatHandle) As Boolean
                                       Dim runtime As MASFloatRuntime = ResolveAdvancedFloatRuntime()
                                       If runtime Is Nothing OrElse handle Is Nothing Then Return False
                                       Try
                                           Return runtime.IsOpen(handle)
                                       Catch masCaughtException As Exception
                                           Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException, "MASSearchTextBox.Advanced.IsOpen")
                                           Return False
                                       End Try
                                   End Function,
                    closeOwnedFloat:=Function(handle As MASFloatHandle) As Boolean
                                         Dim runtime As MASFloatRuntime = ResolveAdvancedFloatRuntime()
                                         If runtime Is Nothing OrElse handle Is Nothing Then Return False
                                         Try
                                             Return runtime.Close(handle, MASFloatCloseReason.Programmatic)
                                         Catch masCaughtException As Exception
                                             Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException, "MASSearchTextBox.Advanced.Close")
                                             Return False
                                         End Try
                                     End Function,
                    showFloat:=Function(ownerKey As String,
                                        content As IMASFloatContent,
                                        boundsPx As SKRect) As MASFloatHandle
                                   Return ShowAdvancedSuggestionFloat(ownerKey, content, boundsPx)
                               End Function)

            AddHandler Me.TextChanged, AddressOf OnAdvancedSearchTextChanged
        End Sub

        Friend Sub BindControlRuntimeServices(services As MASControlRuntimeServices) Implements IMASControlRuntimeServicesConsumer.BindControlRuntimeServices
            If Object.ReferenceEquals(_advancedRuntimeServices, services) Then Return
            CloseAdvancedSuggestionPopup()
            _advancedRuntimeServices = services
        End Sub

#End Region

#Region "Advanced SearchBox event and DataGrid route"

        Private Sub OnAdvancedSearchTextChanged(sender As Object, e As EventArgs)
            ApplyAdvancedDataGridFilter()
            Dim ctx As MASThemeContext = TryGetContext()
            If ctx IsNot Nothing AndAlso Me.HasKeyboardFocus Then ShowAdvancedSuggestions(ctx)
        End Sub

        Private Sub ApplyAdvancedDataGridFilter()
            If _advancedBoundDataGrid Is Nothing Then Return

            Try
                _advancedBoundDataGrid.SetFilterText(EffectiveSearchText)
            Catch masCaughtException As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException, "MASSearchTextBox.Advanced.DataGridFilter")
            End Try
        End Sub

        Private Sub RaiseSearchCommittedSafe()
            Try
                RaiseEvent SearchCommitted(Me, EventArgs.Empty)
            Catch masCaughtException As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtException, "MASSearchTextBox.SearchCommitted")
            End Try
        End Sub

#End Region

#Region "Advanced SearchBox suggestion popup"

        Private Sub ShowAdvancedSuggestions(ctx As MASThemeContext)
            If ctx Is Nothing Then Return
            If _advancedDropDownService Is Nothing Then Return
            If ResolveAdvancedFloatRuntime() Is Nothing Then Return
            If Not Me.Enabled OrElse Not Me.Visible Then Return

            Dim items As ReadOnlyCollection(Of String) = _advancedSearchState.BuildSuggestionPopupItems(Me.Text, MASSearchTextBoxPolicy.NormalizeSuggestionPopupLimit(8))
            If items.Count = 0 Then
                CloseAdvancedSuggestionPopup()
                Return
            End If

            Dim dpi As Single = PixelSnap.SafeDpi(ctx.Dpi)
            Dim anchorBoundsPx As SKRect = ResolveAdvancedAnchorBoundsPx(dpi)
            If anchorBoundsPx.Width <= 1.0F OrElse anchorBoundsPx.Height <= 1.0F Then Return

            Dim popupSizePx As SKSize = CalculateAdvancedPopupSizePx(anchorBoundsPx, items.Count, dpi)
            Dim boundsPx As SKRect = DropDownPositionPolicy.Calculate(anchorBoundsPx,
                                                                      popupSizePx,
                                                                      dpi,
                                                                      4.0F * dpi,
                                                                      Function()
                                                                          Return ResolveAdvancedSurfaceSizePx(anchorBoundsPx)
                                                                      End Function)

            Dim content As IMASFloatContent =
                New MASDropDownListFloatContent(
                    items,
                    -1,
                    Sub(index As Integer, value As String)
                        If index >= 0 Then
                            Me.Text = MASSearchTextBoxPolicy.NormalizeSearchText(value)
                            CommitSearch()
                        End If
                    End Sub,
                    Sub()
                        _advancedSuggestionPopupOpen = True
                        InvalidateVisual()
                    End Sub,
                    Sub()
                        _advancedSuggestionPopupOpen = False
                        InvalidateVisual()
                    End Sub,
                    Function(contract As MASResolvedComponentContract,
                             width As Integer,
                             height As Integer,
                             popupDpi As Single,
                             totalItems As Integer) As ListPopupLayout.Metrics

                        Return ListPopupLayout.Build(contract, width, height, popupDpi, totalItems)
                    End Function,
                    ctx,
                    contextProvider:=Function()
                                         If Me.Host Is Nothing Then Return Nothing
                                         Return Me.Host.TryGetContext()
                                     End Function)

            Try
                _advancedDropDownService.ShowOwned(Me, content, boundsPx)
                _advancedSuggestionPopupOpen = True
            Catch masCaughtException As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException, "MASSearchTextBox.Advanced.ShowSuggestions")
                _advancedSuggestionPopupOpen = False
            End Try
        End Sub

        Private Sub CloseAdvancedSuggestionPopup()
            _advancedSuggestionPopupOpen = False
            If _advancedDropDownService Is Nothing Then Return
            _advancedDropDownService.CloseIfOwnedBy(Me)
        End Sub

        Private Function ShowAdvancedSuggestionFloat(ownerKey As String,
                                                     content As IMASFloatContent,
                                                     boundsPx As SKRect) As MASFloatHandle

            Dim runtime As MASFloatRuntime = ResolveAdvancedFloatRuntime()
            If runtime Is Nothing OrElse content Is Nothing Then Return Nothing
            ownerKey = MASSearchTextBoxPolicy.ResolveSuggestionOwnerKey(ownerKey)

            Dim request As New MASFloatRequest() With {
                .FloatKey = MASFloatKeys.DropDownList,
                .OwnerKey = ownerKey,
                .Content = content,
                .RequestedSlot = MASFloatSlot.Popup,
                .RequestedScope = MASFloatScope.Owner,
                .RequestedLayer = MASFloatLayerLevel.Interactive,
                .ShowPolicy = MASFloatShowPolicy.CloseSameOwnerThenShow,
                .AnchorRectPx = ResolveAdvancedAnchorBoundsPx(1.0F),
                .InitialBoundsPx = boundsPx,
                .AvailableBoundsPx = New SKRect(0.0F, 0.0F, Math.Max(boundsPx.Right, boundsPx.Width), Math.Max(boundsPx.Bottom, boundsPx.Height)),
                .RequestedPresentation = MASFloatPresentationProfiles.DropDownList()
            }

            Return runtime.Show(request)
        End Function

        Private Function ResolveAdvancedFloatRuntime() As MASFloatRuntime
            If _advancedRuntimeServices Is Nothing Then Return Nothing
            Return _advancedRuntimeServices.TryGetFloatRuntime()
        End Function

        Private Function ResolveAdvancedAnchorBoundsPx(dpi As Single) As SKRect
            Dim safeDpi As Single = PixelSnap.SafeDpi(dpi)
            Dim slotAnchorPx As SKRect = GetLayoutFloatAnchorBoundsPxInternal(safeDpi)
            If slotAnchorPx.Width > 1.0F AndAlso slotAnchorPx.Height > 1.0F Then Return slotAnchorPx
            Return SKRect.Empty
        End Function

        Private Function ResolveAdvancedSurfaceSizePx(anchorBoundsPx As SKRect) As SKSizeI
            If _advancedRuntimeServices IsNot Nothing Then
                Dim serviceSize As SKSizeI = _advancedRuntimeServices.TryGetSurfaceSizePx()
                If serviceSize.Width > 1 AndAlso serviceSize.Height > 1 Then Return serviceSize
            End If

            Return New SKSizeI(CInt(Math.Max(320.0F, anchorBoundsPx.Right + 48.0F)),
                               CInt(Math.Max(240.0F, anchorBoundsPx.Bottom + 220.0F)))
        End Function

        Private Shared Function CalculateAdvancedPopupSizePx(anchorBoundsPx As SKRect,
                                                             itemCount As Integer,
                                                             dpi As Single) As SKSize

            Dim safeDpi As Single = PixelSnap.SafeDpi(dpi)
            Dim visibleRows As Integer = MASSearchTextBoxPolicy.ResolveVisibleSuggestionRows(itemCount)
            Dim widthPx As Single = Math.Max(anchorBoundsPx.Width, 220.0F * safeDpi)
            Dim heightPx As Single = (visibleRows * 36.0F + 14.0F) * safeDpi
            Return New SKSize(widthPx, heightPx)
        End Function

#End Region

#Region "Advanced SearchBox lifecycle hooks"

        Protected Overrides Sub OnEnabledChanged()
            CloseAdvancedSuggestionPopup()
            MyBase.OnEnabledChanged()
        End Sub

        Protected Overrides Sub OnVisibleChanged()
            CloseAdvancedSuggestionPopup()
            MyBase.OnVisibleChanged()
        End Sub

        Protected Overrides Sub OnDetached()
            CloseAdvancedSuggestionPopup()
            MyBase.OnDetached()
        End Sub

#End Region

    End Class

End Namespace
