Option Strict On
Option Explicit On

Imports System
Imports Nexamas.UI.Theming
Imports SkiaSharp

Namespace Nexamas.UI.FloatRuntime

    ' ============================================================
    ' MASFloatRuntime Contract
    ' Class: MASFloatHostPresenter
    '
    ' Purpose:
    '   Renders hosted Float records from MASFloatHost onto the Nexamas UI
    '   render pipeline.
    '
    ' Responsibility:
    '   Enumerates hosted records, resolves their bounds from HostState, and
    '   calls IMASFloatRenderableContent when available.
    '
    ' Owns:
    '   No lifecycle state.
    '   No host state.
    '
    ' Does Not Own:
    '   Show/close policy.
    '   Input routing.
    '   Focus ownership.
    '   Result delivery.
    '   ThemeContext.
    '
    ' Allowed Dependencies:
    '   MASFloatHost.
    '   MASFloatHostState.
    '   MASFloatLifecycleRecord.
    '   IMASFloatRenderableContent.
    '   SKCanvas.
    '   ThemeContext.
    '
    ' Forbidden Dependencies:
    '   Legacy Overlay classes.
    '   OverlayHost.
    '   OverlayManager.
    '   OverlayContent.
    '   OverlayLayer.
    '   OverlayService.
    '
    ' Lifecycle Role:
    '   Called during Nexamas UI render pass only.
    '
    ' Threading:
    '   Called on render/UI path.
    '
    ' Mutation Rules:
    '   Must not mutate lifecycle.
    '
    ' Invariants:
    '   Open, Opening, and Closing records may be rendered so runtime-owned close transitions remain visible until completion.
    '   Bounds are pixel-space.
    '
    ' Failure Rules:
    '   Non-renderable content is ignored.
    ' ============================================================
    Friend NotInheritable Class MASFloatHostPresenter

        Friend Sub Render(
            host As MASFloatHost,
            canvas As SKCanvas,
            ctx As MASThemeContext
        )
            If host Is Nothing Then Return
            If canvas Is Nothing OrElse ctx Is Nothing Then Return

            For Each record As MASFloatLifecycleRecord In host.State.Records
                If record Is Nothing Then Continue For

                If record.State <> MASFloatLifecycleState.Open AndAlso
                   record.State <> MASFloatLifecycleState.Opening AndAlso
                   record.State <> MASFloatLifecycleState.Closing Then
                    Continue For
                End If

                Dim content As IMASFloatRenderableContent =
                    TryCast(record.ResolvedRequest.Request.Content, IMASFloatRenderableContent)

                If content Is Nothing Then Continue For

                Dim boundsPx As SKRect = SKRect.Empty

                If Not host.State.TryGetBoundsPx(record.Handle, boundsPx) Then
                    Continue For
                End If

                If boundsPx.Width <= 0.5F OrElse boundsPx.Height <= 0.5F Then
                    Continue For
                End If

                Try
                    RenderFloatWithTransition(record, content, canvas, ctx, boundsPx)
                Catch masCaughtException1 As Exception
                    Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtException1)
                End Try
            Next
        End Sub


        Private Shared Sub RenderFloatWithTransition(record As MASFloatLifecycleRecord,
                                                     content As IMASFloatRenderableContent,
                                                     canvas As SKCanvas,
                                                     ctx As MASThemeContext,
                                                     boundsPx As SKRect)
            If record Is Nothing OrElse content Is Nothing Then Return
            If canvas Is Nothing OrElse ctx Is Nothing Then Return

            If Not record.HasActiveTransitionVisual Then
                content.RenderFloat(canvas, ctx, boundsPx)
                Return
            End If

            Dim opacity As Single = Clamp01(record.TransitionOpacity)
            If opacity <= 0.001F Then Return

            Dim saveCount As Integer = canvas.Save()

            Try
                Dim scale As Single = ClampScale(record.TransitionScale)
                Dim offsetX As Single = record.TransitionOffsetXPx
                Dim offsetY As Single = record.TransitionOffsetYPx

                If Math.Abs(offsetX) > 0.01F OrElse Math.Abs(offsetY) > 0.01F Then
                    canvas.Translate(offsetX, offsetY)
                End If

                If Math.Abs(scale - 1.0F) > 0.0001F Then
                    Dim originX As Single = ResolveTransitionOriginX(record, boundsPx)
                    Dim originY As Single = ResolveTransitionOriginY(record, boundsPx)
                    canvas.Translate(originX, originY)
                    canvas.Scale(scale, scale)
                    canvas.Translate(-originX, -originY)
                End If

                If opacity < 0.999F Then
                    Using paint As New SKPaint()
                        paint.Color = New SKColor(255, 255, 255, CByte(Math.Max(0, Math.Min(255, CInt(Math.Round(opacity * 255.0F))))))
                        canvas.SaveLayer(paint)
                        Try
                            content.RenderFloat(canvas, ctx, boundsPx)
                        Finally
                            canvas.Restore()
                        End Try
                    End Using
                Else
                    content.RenderFloat(canvas, ctx, boundsPx)
                End If
            Finally
                canvas.RestoreToCount(saveCount)
            End Try
        End Sub


        Private Shared Function ResolveTransitionOriginX(record As MASFloatLifecycleRecord,
                                                         boundsPx As SKRect) As Single
            If record IsNot Nothing AndAlso
               Not Single.IsNaN(record.TransitionOriginXPx) AndAlso
               Not Single.IsInfinity(record.TransitionOriginXPx) Then
                Return record.TransitionOriginXPx
            End If

            Return boundsPx.Left + (boundsPx.Width * 0.5F)
        End Function

        Private Shared Function ResolveTransitionOriginY(record As MASFloatLifecycleRecord,
                                                         boundsPx As SKRect) As Single
            If record IsNot Nothing AndAlso
               Not Single.IsNaN(record.TransitionOriginYPx) AndAlso
               Not Single.IsInfinity(record.TransitionOriginYPx) Then
                Return record.TransitionOriginYPx
            End If

            Return boundsPx.Top + (boundsPx.Height * 0.5F)
        End Function

        Private Shared Function Clamp01(value As Single) As Single
            If Single.IsNaN(value) OrElse Single.IsInfinity(value) Then Return 1.0F
            If value < 0.0F Then Return 0.0F
            If value > 1.0F Then Return 1.0F
            Return value
        End Function

        Private Shared Function ClampScale(value As Single) As Single
            If Single.IsNaN(value) OrElse Single.IsInfinity(value) Then Return 1.0F
            If value < 0.85F Then Return 0.85F
            If value > 1.1F Then Return 1.1F
            Return value
        End Function

    End Class

End Namespace