Option Strict On
Option Explicit On

Imports System
Imports System.Runtime.CompilerServices
Imports Nexamas.UI.Diagnostics

Namespace Nexamas.UI.Virtualization

    ''' <summary>
    ''' Stable diagnostic codes for TileBox/DropDown virtualization helper fallbacks.
    ''' </summary>
    ''' <remarks>
    ''' These codes are Friend-owned and intentionally not public SDK API. They make helper
    ''' fallback decisions retained, searchable, and testable instead of relying on an opaque
    ''' swallowed exception line.
    ''' </remarks>
    Friend Enum MASVirtualizationConsumerDiagnosticCode
        None = 0
        TileBoxPointerMoveFallback = 1
        TileBoxPointerDownFallback = 2
        TileBoxPointerUpFallback = 3
        TileBoxWheelFallback = 4
        TileBoxPointerLeaveFallback = 5
        TileBoxPointerCancelFallback = 6
        DropDownContextProviderFallback = 7
        DropDownScrollFactoryFallback = 8
        DropDownInvalidateFallback = 9
        DropDownRestoreScrollOffsetFallback = 10
        DropDownEnsureSelectedVisibleFallback = 11
    End Enum

    ''' <summary>
    ''' Immutable Friend-only report for the most recent virtualization consumer helper fallback.
    ''' </summary>
    Friend NotInheritable Class MASVirtualizationConsumerFaultDiagnosticReport
        Friend Sub New(capturedUtc As DateTimeOffset,
                       faultCount As Integer,
                       lastDiagnosticCode As String,
                       lastConsumerName As String,
                       lastBoundaryContext As String,
                       lastExceptionTypeName As String,
                       lastReason As String)

            Me.CapturedUtc = capturedUtc
            Me.FaultCount = Math.Max(0, faultCount)
            Me.LastDiagnosticCode = If(lastDiagnosticCode, String.Empty)
            Me.LastConsumerName = If(lastConsumerName, String.Empty)
            Me.LastBoundaryContext = If(lastBoundaryContext, String.Empty)
            Me.LastExceptionTypeName = If(lastExceptionTypeName, String.Empty)
            Me.LastReason = If(lastReason, String.Empty)
        End Sub

        Friend ReadOnly Property CapturedUtc As DateTimeOffset
        Friend ReadOnly Property FaultCount As Integer
        Friend ReadOnly Property LastDiagnosticCode As String
        Friend ReadOnly Property LastConsumerName As String
        Friend ReadOnly Property LastBoundaryContext As String
        Friend ReadOnly Property LastExceptionTypeName As String
        Friend ReadOnly Property LastReason As String

        Friend ReadOnly Property HasFaults As Boolean
            Get
                Return FaultCount > 0
            End Get
        End Property
    End Class

    ''' <summary>
    ''' Single Virtualization-owned diagnostic channel for helper fallbacks in TileBox/DropDown consumers.
    ''' </summary>
    Friend NotInheritable Class MASVirtualizationConsumerFaultDiagnostics
        Friend Const ContextPrefix As String = "Virtualization.ConsumerHelperFault."

        Private Shared ReadOnly SyncRoot As New Object()
        Private Shared _faultCount As Integer
        Private Shared _lastDiagnosticCode As MASVirtualizationConsumerDiagnosticCode = MASVirtualizationConsumerDiagnosticCode.None
        Private Shared _lastConsumerName As String = String.Empty
        Private Shared _lastBoundaryContext As String = String.Empty
        Private Shared _lastExceptionTypeName As String = String.Empty
        Private Shared _lastReason As String = String.Empty

        Private Sub New()
        End Sub

        Friend Shared Sub RecordFailure(ex As Exception,
                                        diagnosticCode As MASVirtualizationConsumerDiagnosticCode,
                                        consumerName As String,
                                        boundaryContext As String,
                                        reason As String,
                                        severity As MASExceptionSeverity,
                                        category As MASExceptionHandlingCategory,
                                        <CallerMemberName> Optional memberName As String = Nothing,
                                        <CallerFilePath> Optional sourceFile As String = Nothing,
                                        <CallerLineNumber> Optional sourceLine As Integer = 0)
            If ex Is Nothing Then Return

            Dim normalizedCode As MASVirtualizationConsumerDiagnosticCode = NormalizeCode(diagnosticCode)
            If normalizedCode = MASVirtualizationConsumerDiagnosticCode.None Then Return

            Dim normalizedConsumer As String = Normalize(consumerName)
            If normalizedConsumer.Length = 0 Then normalizedConsumer = "VirtualizationConsumer"

            Dim normalizedBoundary As String = Normalize(boundaryContext)
            If normalizedBoundary.Length = 0 Then normalizedBoundary = If(memberName, "Virtualization.ConsumerBoundary")

            Dim normalizedReason As String = Normalize(reason)
            If normalizedReason.Length = 0 Then normalizedReason = "Virtualization consumer helper fallback was activated after a boundary exception."

            Dim exceptionTypeName As String = ex.GetType().FullName
            Dim occurrenceCount As Integer

            SyncLock SyncRoot
                _faultCount += 1
                occurrenceCount = _faultCount
                _lastDiagnosticCode = normalizedCode
                _lastConsumerName = normalizedConsumer
                _lastBoundaryContext = normalizedBoundary
                _lastExceptionTypeName = If(exceptionTypeName, String.Empty)
                _lastReason = normalizedReason
            End SyncLock

            Dim record As New MASRuntimeFaultRecord(
                timestampUtc:=Nexamas.UI.Timing.MASTimeProvider.UtcNowOffset(),
                severity:=severity,
                category:=category,
                context:=ContextPrefix & normalizedCode.ToString(),
                sourceFile:=If(sourceFile, "<unknown>"),
                sourceLine:=sourceLine,
                exceptionTypeName:=If(exceptionTypeName, String.Empty),
                message:=BuildMessage(normalizedCode, normalizedConsumer, normalizedBoundary, normalizedReason, ex),
                occurrenceCount:=occurrenceCount,
                isFirstOccurrence:=(occurrenceCount = 1))

            MASRuntimeFaultChannel.Publish(record)
        End Sub

        Friend Shared Function GetReport() As MASVirtualizationConsumerFaultDiagnosticReport
            SyncLock SyncRoot
                Return New MASVirtualizationConsumerFaultDiagnosticReport(
                    capturedUtc:=Nexamas.UI.Timing.MASTimeProvider.UtcNowOffset(),
                    faultCount:=_faultCount,
                    lastDiagnosticCode:=If(_lastDiagnosticCode = MASVirtualizationConsumerDiagnosticCode.None, String.Empty, _lastDiagnosticCode.ToString()),
                    lastConsumerName:=_lastConsumerName,
                    lastBoundaryContext:=_lastBoundaryContext,
                    lastExceptionTypeName:=_lastExceptionTypeName,
                    lastReason:=_lastReason)
            End SyncLock
        End Function

        Friend Shared Sub ResetForTests()
            SyncLock SyncRoot
                _faultCount = 0
                _lastDiagnosticCode = MASVirtualizationConsumerDiagnosticCode.None
                _lastConsumerName = String.Empty
                _lastBoundaryContext = String.Empty
                _lastExceptionTypeName = String.Empty
                _lastReason = String.Empty
            End SyncLock
        End Sub

        Private Shared Function NormalizeCode(diagnosticCode As MASVirtualizationConsumerDiagnosticCode) As MASVirtualizationConsumerDiagnosticCode
            Select Case diagnosticCode
                Case MASVirtualizationConsumerDiagnosticCode.TileBoxPointerMoveFallback,
                     MASVirtualizationConsumerDiagnosticCode.TileBoxPointerDownFallback,
                     MASVirtualizationConsumerDiagnosticCode.TileBoxPointerUpFallback,
                     MASVirtualizationConsumerDiagnosticCode.TileBoxWheelFallback,
                     MASVirtualizationConsumerDiagnosticCode.TileBoxPointerLeaveFallback,
                     MASVirtualizationConsumerDiagnosticCode.TileBoxPointerCancelFallback,
                     MASVirtualizationConsumerDiagnosticCode.DropDownContextProviderFallback,
                     MASVirtualizationConsumerDiagnosticCode.DropDownScrollFactoryFallback,
                     MASVirtualizationConsumerDiagnosticCode.DropDownInvalidateFallback,
                     MASVirtualizationConsumerDiagnosticCode.DropDownRestoreScrollOffsetFallback,
                     MASVirtualizationConsumerDiagnosticCode.DropDownEnsureSelectedVisibleFallback
                    Return diagnosticCode
                Case Else
                    Return MASVirtualizationConsumerDiagnosticCode.None
            End Select
        End Function

        Private Shared Function BuildMessage(diagnosticCode As MASVirtualizationConsumerDiagnosticCode,
                                             consumerName As String,
                                             boundaryContext As String,
                                             reason As String,
                                             ex As Exception) As String
            Return "DiagnosticCode=" & diagnosticCode.ToString() &
                   "; Consumer=" & If(consumerName, String.Empty) &
                   "; Boundary=" & If(boundaryContext, String.Empty) &
                   "; Reason=" & If(reason, String.Empty) &
                   "; Exception=" & If(ex Is Nothing, String.Empty, ex.Message)
        End Function

        Private Shared Function Normalize(value As String) As String
            Return If(value, String.Empty).Trim()
        End Function
    End Class

End Namespace
