Option Strict On
Option Explicit On

Imports System
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.Controls
Imports Nexamas.UI.General
Imports Nexamas.UI.Rendering
Imports Nexamas.UI.Theming
Imports Nexamas.UI.TextRendering
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Components


    Partial Friend NotInheritable Class MASTabRenderer
        Friend Sub Render(canvas As SKCanvas,
                  ctx As MASThemeContext,
                  owner As MASTabControl,
                  contract As MASResolvedComponentContract,
                  pixelBounds As SKRect)

            If _disposed Then Return
            If canvas Is Nothing OrElse ctx Is Nothing OrElse owner Is Nothing OrElse contract Is Nothing Then Return

            ' The TabControl descriptor intentionally exposes the structural layers that the
            ' architecture resolver can guarantee for a selector strip: Surface, Border,
            ' Text, Hover, Pressed, and Focus. Selection is a component state rendered
            ' through the selectable interaction contract, not a globally resolved layer
            ' in the current architecture profile; Shadow is not declared for tabs.
            ' Requiring either one here makes ControlsLayer swallow the renderer exception
            ' and leaves the tab strip visually invisible.
            Dim scope As MASContractRenderScope =
    MASArchitectureRuntime.CreateRenderScopeForComponent(Of MASTabControl)(
        NameOf(MASTabRenderer),
        contract,
        MASRenderLayer.Surface,
        MASRenderLayer.Border,
        MASRenderLayer.Text,
        MASRenderLayer.Hover,
        MASRenderLayer.Pressed,
        MASRenderLayer.Focus)

            scope.RequireAnyLayer(
    MASRenderLayer.Surface,
    MASRenderLayer.Border,
    MASRenderLayer.Text,
    MASRenderLayer.Hover,
    MASRenderLayer.Pressed,
    MASRenderLayer.Focus)

            If pixelBounds.Width <= 1.0F OrElse pixelBounds.Height <= 1.0F Then Return
            If owner.Items.Count <= 0 Then Return

            Dim env As New MASTabRenderEnv()
            If Not TryBuildEnv(ctx, env) Then Return

            Dim plan As MASTabControlLayoutPlan = MASTabControlLayoutPlan.Create(ctx, pixelBounds, owner.SizeIntent)
            Dim metricDpi As Single = If(plan Is Nothing, env.Dpi, plan.MetricDpi)

            Dim lay As MASTabLayout.Metrics =
                MASTabLayout.Compute(
                    fullBoundsPx:=pixelBounds,
                    dpi:=metricDpi,
                    items:=owner.Items,
                    measureTextWidth:=Function(text As String) MeasureTabTextWidth(text, metricDpi),
                    requestedScrollOffsetPx:=owner.ScrollOffsetPx
                )

            owner.UpdateScrollMetrics(lay.MaxScrollOffset, lay.ViewportW)

            If owner.ConsumeEnsureSelectedVisibleRequest() AndAlso owner.SelectedIndex >= 0 Then
                Dim desiredOffset As Single =
                    MASTabLayout.GetScrollOffsetToRevealItem(lay, owner.SelectedIndex)

                owner.RequestEnsureSelectedVisible(desiredOffset)

                lay = MASTabLayout.Compute(
                    fullBoundsPx:=pixelBounds,
                    dpi:=metricDpi,
                    items:=owner.Items,
                    measureTextWidth:=Function(text As String) MeasureTabTextWidth(text, metricDpi),
                    requestedScrollOffsetPx:=owner.ScrollOffsetPx
                )

                owner.UpdateScrollMetrics(lay.MaxScrollOffset, lay.ViewportW)
            End If

            _state.DrawStrip(canvas, lay, lay.StripR, env)

            If lay.HasOverflow Then
                DrawViewportPlate(canvas, lay, contract, env)
                DrawNavBaySeams(canvas, lay, env)

                DrawNavButton(canvas, ctx, lay, lay.LeftNavR, MASTabNavButtonKind.LeftButton, owner, env)
                DrawNavButton(canvas, ctx, lay, lay.RightNavR, MASTabNavButtonKind.RightButton, owner, env)
            Else
                DrawViewportPlate(canvas, lay, contract, env)
            End If

            Dim saveCount As Integer = canvas.Save()
            Try
                canvas.ClipRect(lay.ViewportR)

                For i As Integer = 0 To owner.Items.Count - 1
                    Dim item As MASTabItem = owner.Items(i)
                    If item Is Nothing Then Continue For

                    Dim itemR As SKRect = MASTabLayout.ComputeItemRect(lay, i)
                    Dim visibleR As SKRect = IntersectRect(itemR, lay.ViewportR)

                    If visibleR.Width <= 1.0F OrElse visibleR.Height <= 1.0F Then Continue For

                    Dim st As MASVisualInteractionState = ResolveItemState(owner, i, item.Enabled)
                    Dim showFocusCue As Boolean =
                        item.Enabled AndAlso
                        owner.HasKeyboardFocus AndAlso
                        i = owner.SelectedIndex AndAlso
                        Not owner.IsSelectionIndicatorMotionActive()

                    _state.DrawItem(
                        canvas:=canvas,
                        lay:=lay,
                        itemR:=itemR,
                        st:=st,
                        isEnabled:=item.Enabled,
                        showFocusCue:=showFocusCue,
                        env:=env
                    )

                    DrawItemText(canvas, ctx, lay, itemR, item.Text, st, item.Enabled, env)
                Next

                DrawSelectionIndicatorMotionItem(canvas, ctx, lay, owner, env)
            Finally
                canvas.RestoreToCount(saveCount)
            End Try

            If lay.HasOverflow Then
                DrawViewportEdgeFade(canvas, lay, env)
            End If
        End Sub




        Private Sub DrawSelectionIndicatorMotionItem(canvas As SKCanvas,
                                                     ctx As MASThemeContext,
                                                     lay As MASTabLayout.Metrics,
                                                     owner As MASTabControl,
                                                     env As MASTabRenderEnv)

            If canvas Is Nothing OrElse ctx Is Nothing OrElse owner Is Nothing Then Return
            If Not owner.IsSelectionIndicatorMotionActive() Then Return

            Dim index As Integer = owner.SelectionIndicatorMotionToIndex
            If index < 0 OrElse index >= owner.Items.Count Then Return

            Dim item As MASTabItem = owner.Items(index)
            If item Is Nothing OrElse Not item.Enabled Then Return

            Dim itemR As SKRect = owner.ResolveSelectionIndicatorMotionRect(lay)
            Dim visibleR As SKRect = IntersectRect(itemR, lay.ViewportR)
            If visibleR.Width <= 1.0F OrElse visibleR.Height <= 1.0F Then Return

            _state.DrawItem(
                canvas:=canvas,
                lay:=lay,
                itemR:=itemR,
                st:=MASVisualInteractionState.Selected,
                isEnabled:=True,
                showFocusCue:=owner.HasKeyboardFocus,
                env:=env)

            DrawItemText(canvas, ctx, lay, itemR, item.Text, MASVisualInteractionState.Selected, True, env)
        End Sub




        Friend Function HitIndex(ctx As MASThemeContext,
                                 pixelBounds As SKRect,
                                 ptPx As SKPoint,
                                 owner As MASTabControl) As Integer

            If _disposed Then Return -1
            If ctx Is Nothing OrElse owner Is Nothing Then Return -1
            If owner.Items.Count <= 0 Then Return -1

            Dim dpi As Single = PixelSnap.SafeDpi(ctx.Dpi)
            Dim plan As MASTabControlLayoutPlan = MASTabControlLayoutPlan.Create(ctx, pixelBounds, owner.SizeIntent)
            Dim metricDpi As Single = If(plan Is Nothing, dpi, plan.MetricDpi)

            Dim lay As MASTabLayout.Metrics =
                MASTabLayout.Compute(
                    fullBoundsPx:=pixelBounds,
                    dpi:=metricDpi,
                    items:=owner.Items,
                    measureTextWidth:=Function(text As String) MeasureTabTextWidth(text, metricDpi),
                    requestedScrollOffsetPx:=owner.ScrollOffsetPx
                )

            Return MASTabLayout.HitIndex(lay, ptPx)
        End Function




        Friend Function HitNavButton(ctx As MASThemeContext,
                                     pixelBounds As SKRect,
                                     ptPx As SKPoint,
                                     owner As MASTabControl) As MASTabNavButtonKind

            If _disposed Then Return MASTabNavButtonKind.None
            If ctx Is Nothing OrElse owner Is Nothing Then Return MASTabNavButtonKind.None
            If owner.Items.Count <= 0 Then Return MASTabNavButtonKind.None

            Dim dpi As Single = PixelSnap.SafeDpi(ctx.Dpi)
            Dim plan As MASTabControlLayoutPlan = MASTabControlLayoutPlan.Create(ctx, pixelBounds, owner.SizeIntent)
            Dim metricDpi As Single = If(plan Is Nothing, dpi, plan.MetricDpi)

            Dim lay As MASTabLayout.Metrics =
                MASTabLayout.Compute(
                    fullBoundsPx:=pixelBounds,
                    dpi:=metricDpi,
                    items:=owner.Items,
                    measureTextWidth:=Function(text As String) MeasureTabTextWidth(text, metricDpi),
                    requestedScrollOffsetPx:=owner.ScrollOffsetPx
                )

            Return MASTabLayout.HitNavButton(lay, ptPx)
        End Function




        Private Function TryBuildEnv(ctx As MASThemeContext,
                                     ByRef env As MASTabRenderEnv) As Boolean

            If ctx Is Nothing Then Return False
            If ctx.Theme Is Nothing Then Return False
            If MASAppThemeContracts.Foundation(ctx.Theme).Surface Is Nothing Then Return False

            Dim tabTheme As IMASTabTheme = ResolveTabTheme(ctx)
            If Not IsCompleteTabTheme(tabTheme) Then Return False

            env.ThemeContext = ctx
            env.Dpi = PixelSnap.SafeDpi(ctx.Dpi)
            env.RingMetrics = MASSurfaceInteractionRing.GetMetrics(env.Dpi)

            env.AppTheme = ctx.Theme
            env.Foundation = MASAppThemeContracts.Foundation(ctx.Theme)
            env.Surface = MASAppThemeContracts.Foundation(ctx.Theme).Surface
            env.Tabs = tabTheme

            Return True
        End Function




        Private Function ResolveTabTheme(ctx As MASThemeContext) As IMASTabTheme
            If ctx Is Nothing OrElse ctx.Theme Is Nothing Then Return Nothing

            Dim currentTheme As IMASTabTheme = Nothing
            Try
                currentTheme = ctx.TabTheme
            Catch masCaughtException As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowRenderFallback(
                    masCaughtException,
                    "MASTabRenderer.ResolveTabTheme.Current")
            End Try

            If IsCompleteTabTheme(currentTheme) Then Return currentTheme

            Dim themeId As String = If(ctx.Theme.Id, String.Empty)
            If _fallbackTheme IsNot Nothing AndAlso
               String.Equals(_fallbackThemeId, themeId, StringComparison.OrdinalIgnoreCase) Then
                Return _fallbackTheme
            End If

            Try
                _fallbackTheme = New TabTheme(MASSignatureTabThemeRecipeFactory.Create(MASAppThemeContracts.Foundation(ctx.Theme)))
                _fallbackThemeId = themeId
                Return _fallbackTheme
            Catch masCaughtException As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowRenderFallback(
                    masCaughtException,
                    "MASTabRenderer.ResolveTabThemeFallback")

                _fallbackTheme = Nothing
                _fallbackThemeId = String.Empty
                Return currentTheme
            End Try
        End Function




        Private Shared Function IsCompleteTabTheme(tabTheme As IMASTabTheme) As Boolean
            If tabTheme Is Nothing Then Return False
            If tabTheme.Strip Is Nothing Then Return False
            If tabTheme.Item Is Nothing Then Return False
            If tabTheme.Nav Is Nothing Then Return False
            If tabTheme.Text Is Nothing Then Return False
            Return True
        End Function




        Private Shared Function ResolveItemState(owner As MASTabControl,
                                                 index As Integer,
                                                 isEnabled As Boolean) As MASVisualInteractionState

            If Not isEnabled Then Return MASVisualInteractionState.Disabled
            If index = owner.PressedIndex Then Return MASVisualInteractionState.Pressed
            If owner.IsSelectionIndicatorMotionActive() AndAlso index = owner.SelectionIndicatorMotionToIndex Then
                If index = owner.HoverIndex Then Return MASVisualInteractionState.Hover
                Return MASVisualInteractionState.Normal
            End If
            If index = owner.SelectedIndex Then Return MASVisualInteractionState.Selected
            If index = owner.HoverIndex Then Return MASVisualInteractionState.Hover
            Return MASVisualInteractionState.Normal
        End Function

    End Class

End Namespace
