Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
Imports System.Globalization
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.Certification

Namespace Nexamas.UI.Verification

    ''' <summary>
    ''' Severity for the Render Verification element-coverage closure audit.
    ''' </summary>
    Friend Enum MASRenderVerificationElementCoverageClosureFindingSeverity
        Info = 0
        Warning = 1
        [Error] = 2
    End Enum

    ''' <summary>
    ''' Immutable finding emitted by the Render Verification element-coverage closure audit.
    ''' </summary>
    Friend NotInheritable Class MASRenderVerificationElementCoverageClosureFinding
        Private Sub New(severity As MASRenderVerificationElementCoverageClosureFindingSeverity,
                        code As String,
                        subject As String,
                        message As String)
            Me.Severity = severity
            Me.Code = NormalizeText(code)
            Me.Subject = NormalizeText(subject)
            Me.Message = NormalizeText(message)
        End Sub

        Friend ReadOnly Property Severity As MASRenderVerificationElementCoverageClosureFindingSeverity
        Friend ReadOnly Property Code As String
        Friend ReadOnly Property Subject As String
        Friend ReadOnly Property Message As String

        Friend Shared Function Info(code As String,
                                    subject As String,
                                    message As String) As MASRenderVerificationElementCoverageClosureFinding
            Return New MASRenderVerificationElementCoverageClosureFinding(MASRenderVerificationElementCoverageClosureFindingSeverity.Info, code, subject, message)
        End Function

        Friend Shared Function Warning(code As String,
                                       subject As String,
                                       message As String) As MASRenderVerificationElementCoverageClosureFinding
            Return New MASRenderVerificationElementCoverageClosureFinding(MASRenderVerificationElementCoverageClosureFindingSeverity.Warning, code, subject, message)
        End Function

        Friend Shared Function [Error](code As String,
                                       subject As String,
                                       message As String) As MASRenderVerificationElementCoverageClosureFinding
            Return New MASRenderVerificationElementCoverageClosureFinding(MASRenderVerificationElementCoverageClosureFindingSeverity.Error, code, subject, message)
        End Function

        Public Overrides Function ToString() As String
            Return Severity.ToString() & " | " & Code & " | " & Subject & " | " & Message
        End Function

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

    ''' <summary>
    ''' Immutable report for dashboard-level element coverage closure.
    ''' </summary>
    Friend NotInheritable Class MASRenderVerificationElementCoverageClosureReport
        Private ReadOnly _findings As ReadOnlyCollection(Of MASRenderVerificationElementCoverageClosureFinding)

        Friend Sub New(descriptorCount As Integer,
                       componentCoverageRowCount As Integer,
                       dashboardRowCount As Integer,
                       targetCount As Integer,
                       scenarioCount As Integer,
                       captureSceneCount As Integer,
                       directVisualRouteCount As Integer,
                       familyVisualRouteCount As Integer,
                       contractProofRouteCount As Integer,
                       findings As IEnumerable(Of MASRenderVerificationElementCoverageClosureFinding))
            Me.DescriptorCount = Math.Max(0, descriptorCount)
            Me.ComponentCoverageRowCount = Math.Max(0, componentCoverageRowCount)
            Me.DashboardRowCount = Math.Max(0, dashboardRowCount)
            Me.TargetCount = Math.Max(0, targetCount)
            Me.ScenarioCount = Math.Max(0, scenarioCount)
            Me.CaptureSceneCount = Math.Max(0, captureSceneCount)
            Me.DirectVisualRouteCount = Math.Max(0, directVisualRouteCount)
            Me.FamilyVisualRouteCount = Math.Max(0, familyVisualRouteCount)
            Me.ContractProofRouteCount = Math.Max(0, contractProofRouteCount)

            Dim normalized As New List(Of MASRenderVerificationElementCoverageClosureFinding)()
            If findings IsNot Nothing Then
                For Each finding As MASRenderVerificationElementCoverageClosureFinding In findings
                    If finding IsNot Nothing Then normalized.Add(finding)
                Next
            End If

            _findings = New ReadOnlyCollection(Of MASRenderVerificationElementCoverageClosureFinding)(normalized)
        End Sub

        Friend ReadOnly Property DescriptorCount As Integer
        Friend ReadOnly Property ComponentCoverageRowCount As Integer
        Friend ReadOnly Property DashboardRowCount As Integer
        Friend ReadOnly Property TargetCount As Integer
        Friend ReadOnly Property ScenarioCount As Integer
        Friend ReadOnly Property CaptureSceneCount As Integer
        Friend ReadOnly Property DirectVisualRouteCount As Integer
        Friend ReadOnly Property FamilyVisualRouteCount As Integer
        Friend ReadOnly Property ContractProofRouteCount As Integer

        Friend ReadOnly Property Findings As ReadOnlyCollection(Of MASRenderVerificationElementCoverageClosureFinding)
            Get
                Return _findings
            End Get
        End Property

        Friend ReadOnly Property ErrorCount As Integer
            Get
                Return CountBySeverity(MASRenderVerificationElementCoverageClosureFindingSeverity.Error)
            End Get
        End Property

        Friend ReadOnly Property WarningCount As Integer
            Get
                Return CountBySeverity(MASRenderVerificationElementCoverageClosureFindingSeverity.Warning)
            End Get
        End Property

        Friend ReadOnly Property IsClosed As Boolean
            Get
                Return DescriptorCount > 0 AndAlso
                       ComponentCoverageRowCount = DescriptorCount AndAlso
                       DashboardRowCount = ComponentCoverageRowCount AndAlso
                       TargetCount > 0 AndAlso
                       ScenarioCount > 0 AndAlso
                       CaptureSceneCount > 0 AndAlso
                       ErrorCount = 0
            End Get
        End Property

        Friend ReadOnly Property Summary As String
            Get
                Return "Render Verification element coverage closure: " &
                       DescriptorCount.ToString(CultureInfo.InvariantCulture) & " descriptor(s), " &
                       ComponentCoverageRowCount.ToString(CultureInfo.InvariantCulture) & " coverage row(s), " &
                       DashboardRowCount.ToString(CultureInfo.InvariantCulture) & " dashboard row(s), " &
                       DirectVisualRouteCount.ToString(CultureInfo.InvariantCulture) & " direct visual route(s), " &
                       FamilyVisualRouteCount.ToString(CultureInfo.InvariantCulture) & " family visual route(s), " &
                       ContractProofRouteCount.ToString(CultureInfo.InvariantCulture) & " contract proof route(s), " &
                       ErrorCount.ToString(CultureInfo.InvariantCulture) & " error(s), " &
                       WarningCount.ToString(CultureInfo.InvariantCulture) & " warning(s)."
            End Get
        End Property

        Friend Function CreateFindingDetails(maxItems As Integer) As IReadOnlyList(Of String)
            Dim details As New List(Of String)()
            Dim limit As Integer = Math.Max(1, maxItems)

            For Each finding As MASRenderVerificationElementCoverageClosureFinding In _findings
                If finding Is Nothing Then Continue For
                details.Add(finding.ToString())
                If details.Count >= limit Then Exit For
            Next

            Return New ReadOnlyCollection(Of String)(details)
        End Function

        Friend Function CreateSummaryDetails() As IReadOnlyList(Of String)
            Dim details As New List(Of String) From {
                Summary,
                "Descriptor route: MASComponentRegistry.Components remains the only source of component identity.",
                "Coverage route: MASQualityComponentCoverageMap classifies every descriptor into direct, family, or contract/system proof evidence.",
                "Dashboard route: MASRenderVerificationElementCoverageBuilder produces one dashboard row per coverage row, so the Render Verification page cannot show only a partial element list.",
                "Visual route: direct and family rows must resolve to official catalog scenarios and registered capture scenes; contract rows must resolve to named proof routes."
            }

            For Each finding As MASRenderVerificationElementCoverageClosureFinding In _findings
                If finding Is Nothing Then Continue For
                If finding.Severity <> MASRenderVerificationElementCoverageClosureFindingSeverity.Info AndAlso details.Count < 18 Then details.Add(finding.ToString())
            Next

            Return New ReadOnlyCollection(Of String)(details)
        End Function

        Private Function CountBySeverity(severity As MASRenderVerificationElementCoverageClosureFindingSeverity) As Integer
            Dim count As Integer = 0
            For Each finding As MASRenderVerificationElementCoverageClosureFinding In _findings
                If finding IsNot Nothing AndAlso finding.Severity = severity Then count += 1
            Next
            Return count
        End Function
    End Class

    ''' <summary>
    ''' Phase-6 closure audit for the Render Verification element coverage read model.
    ''' It does not run screenshots or create a second catalog. It proves that the existing catalog,
    ''' capture-scene registry, component coverage map, and dashboard row builder describe the same
    ''' component universe before visual snapshots are trusted.
    ''' </summary>
    Friend NotInheritable Class MASRenderVerificationElementCoverageClosureAudit
        Private Sub New()
        End Sub

        Friend Shared Function VerifyCurrent() As MASRenderVerificationElementCoverageClosureReport
            Dim findings As New List(Of MASRenderVerificationElementCoverageClosureFinding)()
            Dim targets As IReadOnlyList(Of MASRenderVerificationTarget) = MASRenderVerificationTargetCatalog.CreateAllTargets()
            Dim targetIndex As TargetScenarioIndex = TargetScenarioIndex.Build(targets)
            Dim descriptors As IReadOnlyList(Of MASComponentDescriptor) = MASComponentRegistry.Components
            Dim descriptorNames As Dictionary(Of String, MASComponentDescriptor) = BuildDescriptorNameMap(descriptors, findings)
            Dim componentCoverage As MASQualityComponentCoverageReport = MASQualityComponentCoverageMap.BuildCurrent()
            Dim graphReport As MASRenderVerificationCoverageReport = MASRenderVerificationCoverageAudit.Build(targets, componentCoverage)
            Dim dashboardReport As MASRenderVerificationElementCoverageReport = MASRenderVerificationElementCoverageBuilder.Build(componentCoverage, graphReport, Nothing)
            Dim dashboardRows As Dictionary(Of String, MASRenderVerificationElementCoverageEntry) = BuildDashboardRowMap(dashboardReport, findings)
            Dim coverageNames As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase)
            Dim directVisualRouteCount As Integer = 0
            Dim familyVisualRouteCount As Integer = 0
            Dim contractProofRouteCount As Integer = 0

            If targets Is Nothing OrElse targets.Count = 0 Then
                findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                    "MASRenderVerification.ElementCoverage.NoTargets",
                    "MASRenderVerificationTargetCatalog",
                    "The official Render Verification target catalog returned no targets."))
            End If

            If componentCoverage Is Nothing OrElse componentCoverage.Entries Is Nothing Then
                findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                    "MASRenderVerification.ElementCoverage.NoComponentCoverage",
                    "MASQualityComponentCoverageMap",
                    "The component coverage map returned no readable entries."))
            Else
                For Each coverageEntry As MASQualityComponentCoverageEntry In componentCoverage.Entries
                    If coverageEntry Is Nothing Then
                        findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                            "MASRenderVerification.ElementCoverage.NullCoverageRow",
                            "MASQualityComponentCoverageMap",
                            "A null component coverage row cannot be displayed by the Render Verification dashboard."))
                        Continue For
                    End If

                    Dim componentKey As String = NormalizeCoverageKey(coverageEntry.ComponentFullName, coverageEntry.ComponentTypeName)
                    If Not coverageNames.Add(componentKey) Then
                        findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                            "MASRenderVerification.ElementCoverage.DuplicateCoverageRow",
                            coverageEntry.ComponentTypeName,
                            "Component coverage contains a duplicate descriptor row."))
                    End If

                    If Not descriptorNames.ContainsKey(componentKey) Then
                        findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                            "MASRenderVerification.ElementCoverage.UnknownCoverageDescriptor",
                            coverageEntry.ComponentTypeName,
                            "Component coverage row is not backed by MASComponentRegistry.Components."))
                    End If

                    Dim dashboardEntry As MASRenderVerificationElementCoverageEntry = Nothing
                    If Not dashboardRows.TryGetValue(componentKey, dashboardEntry) Then
                        findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                            "MASRenderVerification.ElementCoverage.MissingDashboardRow",
                            coverageEntry.ComponentTypeName,
                            "MASRenderVerificationElementCoverageBuilder did not produce a dashboard row for this descriptor."))
                    End If

                    If coverageEntry.HasBlockingGap Then
                        findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                            "MASRenderVerification.ElementCoverage.BlockingCoverageGap",
                            coverageEntry.ComponentTypeName,
                            "Component coverage row is unclassified or lacks evidence: " & coverageEntry.ToString()))
                        Continue For
                    End If

                    Select Case coverageEntry.CoverageKind
                        Case MASQualityComponentCoverageKind.DirectRenderScenario
                            directVisualRouteCount += 1
                            VerifyRenderScenarioRoute(coverageEntry, dashboardEntry, targetIndex, findings)
                        Case MASQualityComponentCoverageKind.FamilyRenderScenario
                            familyVisualRouteCount += 1
                            VerifyRenderScenarioRoute(coverageEntry, dashboardEntry, targetIndex, findings)
                        Case MASQualityComponentCoverageKind.AtomicContractProof,
                             MASQualityComponentCoverageKind.SizeLayoutContractProof,
                             MASQualityComponentCoverageKind.SurfaceMaterialProof,
                             MASQualityComponentCoverageKind.InteractionStateProof,
                             MASQualityComponentCoverageKind.SystemInventoryProof
                            contractProofRouteCount += 1
                            VerifyContractProofRoute(coverageEntry, dashboardEntry, targetIndex, findings)
                        Case MASQualityComponentCoverageKind.OutOfScopeWithReason
                            findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                                "MASRenderVerification.ElementCoverage.OutOfScopeRow",
                                coverageEntry.ComponentTypeName,
                                "Render Verification element closure does not allow a registered descriptor to be hidden as out-of-scope."))
                        Case Else
                            findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                                "MASRenderVerification.ElementCoverage.UnknownCoverageKind",
                                coverageEntry.ComponentTypeName,
                                "Coverage kind is not accepted by the Render Verification element coverage dashboard."))
                    End Select
                Next
            End If

            If graphReport Is Nothing OrElse Not graphReport.IsComplete Then
                Dim graphFindingCount As Integer = If(graphReport Is Nothing, 1, Math.Max(1, graphReport.FindingCount))
                findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                    "MASRenderVerification.ElementCoverage.GraphIncomplete",
                    "MASRenderVerificationCoverageAudit",
                    "The target/scenario/capture/component coverage graph has " & graphFindingCount.ToString(CultureInfo.InvariantCulture) & " blocking finding(s)."))
            End If

            If descriptors IsNot Nothing AndAlso componentCoverage IsNot Nothing AndAlso componentCoverage.ComponentCount <> descriptors.Count Then
                findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                    "MASRenderVerification.ElementCoverage.DescriptorCoverageCountMismatch",
                    "MASComponentRegistry",
                    "Component coverage row count does not match the registered descriptor count."))
            End If

            If componentCoverage IsNot Nothing AndAlso dashboardReport IsNot Nothing AndAlso dashboardReport.ComponentCount <> componentCoverage.ComponentCount Then
                findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                    "MASRenderVerification.ElementCoverage.DashboardCountMismatch",
                    "MASRenderVerificationElementCoverageBuilder",
                    "Dashboard element row count does not match the component coverage row count."))
            End If

            If findings.Count = 0 Then
                findings.Add(MASRenderVerificationElementCoverageClosureFinding.Info(
                    "MASRenderVerification.ElementCoverage.NoFindings",
                    "RenderVerificationSystem",
                    "Render Verification element coverage closure completed without warning or error findings."))
            End If

            Return New MASRenderVerificationElementCoverageClosureReport(
                descriptorCount:=If(descriptors Is Nothing, 0, descriptors.Count),
                componentCoverageRowCount:=If(componentCoverage Is Nothing, 0, componentCoverage.ComponentCount),
                dashboardRowCount:=If(dashboardReport Is Nothing, 0, dashboardReport.ComponentCount),
                targetCount:=targetIndex.TargetCount,
                scenarioCount:=targetIndex.ScenarioCount,
                captureSceneCount:=MASRenderVerificationShowcaseCapture.RegisteredSceneCount,
                directVisualRouteCount:=directVisualRouteCount,
                familyVisualRouteCount:=familyVisualRouteCount,
                contractProofRouteCount:=contractProofRouteCount,
                findings:=findings)
        End Function

        Private Shared Sub VerifyRenderScenarioRoute(coverageEntry As MASQualityComponentCoverageEntry,
                                                     dashboardEntry As MASRenderVerificationElementCoverageEntry,
                                                     targetIndex As TargetScenarioIndex,
                                                     findings As IList(Of MASRenderVerificationElementCoverageClosureFinding))
            If coverageEntry Is Nothing Then Return

            If String.IsNullOrWhiteSpace(coverageEntry.TargetId) Then
                findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                    "MASRenderVerification.ElementCoverage.VisualRouteMissingTarget",
                    coverageEntry.ComponentTypeName,
                    "Direct/family render coverage must name the official target id."))
            ElseIf Not targetIndex.ContainsTarget(coverageEntry.TargetId) Then
                findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                    "MASRenderVerification.ElementCoverage.VisualRouteUnknownTarget",
                    coverageEntry.ComponentTypeName,
                    "Direct/family render coverage references a target that is not in MASRenderVerificationTargetCatalog: " & coverageEntry.TargetId))
            End If

            Dim scenarioFact As ScenarioFact = Nothing
            If String.IsNullOrWhiteSpace(coverageEntry.ScenarioId) Then
                findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                    "MASRenderVerification.ElementCoverage.VisualRouteMissingScenario",
                    coverageEntry.ComponentTypeName,
                    "Direct/family render coverage must name the official scenario id."))
            ElseIf Not targetIndex.TryGetScenario(coverageEntry.ScenarioId, scenarioFact) Then
                findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                    "MASRenderVerification.ElementCoverage.VisualRouteUnknownScenario",
                    coverageEntry.ComponentTypeName,
                    "Direct/family render coverage references a scenario that is not in MASRenderVerificationTargetCatalog: " & coverageEntry.ScenarioId))
            Else
                If Not String.Equals(scenarioFact.TargetId, coverageEntry.TargetId, StringComparison.OrdinalIgnoreCase) Then
                    findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                        "MASRenderVerification.ElementCoverage.VisualRouteWrongTarget",
                        coverageEntry.ComponentTypeName,
                        "Scenario '" & coverageEntry.ScenarioId & "' belongs to target '" & scenarioFact.TargetId & "', not '" & coverageEntry.TargetId & "'."))
                End If

                If Not MASRenderVerificationShowcaseCapture.HasRegisteredScene(scenarioFact.ComponentKey) Then
                    findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                        "MASRenderVerification.ElementCoverage.VisualRouteMissingCaptureScene",
                        coverageEntry.ComponentTypeName,
                        "Scenario component key '" & scenarioFact.ComponentKey & "' has no registered capture scene."))
                End If
            End If

            If dashboardEntry Is Nothing Then Return

            If Not String.Equals(dashboardEntry.TargetId, coverageEntry.TargetId, StringComparison.OrdinalIgnoreCase) Then
                findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                    "MASRenderVerification.ElementCoverage.DashboardTargetMismatch",
                    coverageEntry.ComponentTypeName,
                    "Dashboard row target id does not match the component coverage route."))
            End If

            If Not String.Equals(dashboardEntry.ScenarioId, coverageEntry.ScenarioId, StringComparison.OrdinalIgnoreCase) Then
                findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                    "MASRenderVerification.ElementCoverage.DashboardScenarioMismatch",
                    coverageEntry.ComponentTypeName,
                    "Dashboard row scenario id does not match the component coverage route."))
            End If
        End Sub

        Private Shared Sub VerifyContractProofRoute(coverageEntry As MASQualityComponentCoverageEntry,
                                                    dashboardEntry As MASRenderVerificationElementCoverageEntry,
                                                    targetIndex As TargetScenarioIndex,
                                                    findings As IList(Of MASRenderVerificationElementCoverageClosureFinding))
            If coverageEntry Is Nothing Then Return

            If String.IsNullOrWhiteSpace(coverageEntry.EvidenceKey) Then
                findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                    "MASRenderVerification.ElementCoverage.ContractProofMissingEvidence",
                    coverageEntry.ComponentTypeName,
                    "Contract/system proof coverage must expose a stable evidence key."))
            End If

            If dashboardEntry Is Nothing Then Return

            If dashboardEntry.State <> MASRenderVerificationElementCoverageState.ContractProof Then
                findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                    "MASRenderVerification.ElementCoverage.ContractDashboardStateMismatch",
                    coverageEntry.ComponentTypeName,
                    "A contract/system proof row must be displayed as ContractProof when no snapshot is supplied."))
            End If

            If String.IsNullOrWhiteSpace(dashboardEntry.TargetId) Then
                If coverageEntry.CoverageKind = MASQualityComponentCoverageKind.AtomicContractProof Then
                    findings.Add(MASRenderVerificationElementCoverageClosureFinding.Warning(
                        "MASRenderVerification.ElementCoverage.AtomicContractOnlyRoute",
                        coverageEntry.ComponentTypeName,
                        "Atomic contract proof remains descriptor-only; this is allowed but monitored because no PNG target is required for this route."))
                ElseIf coverageEntry.CoverageKind = MASQualityComponentCoverageKind.SystemInventoryProof Then
                    findings.Add(MASRenderVerificationElementCoverageClosureFinding.Info(
                        "MASRenderVerification.ElementCoverage.SystemInventoryReadModelRoute",
                        coverageEntry.ComponentTypeName,
                        "System inventory proof is displayed through the Render Verification dashboard read model/DataGrid; no PNG target is required for this route."))
                Else
                    findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                        "MASRenderVerification.ElementCoverage.ContractProofMissingTarget",
                        coverageEntry.ComponentTypeName,
                        "Contract proof row must resolve to a proof target unless it is explicitly atomic or system-inventory read-model proof."))
                End If
            ElseIf Not targetIndex.ContainsTarget(dashboardEntry.TargetId) Then
                findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                    "MASRenderVerification.ElementCoverage.ContractProofUnknownTarget",
                    coverageEntry.ComponentTypeName,
                    "Contract/system proof row resolves to an unknown target: " & dashboardEntry.TargetId))
            End If
        End Sub

        Private Shared Function BuildDescriptorNameMap(descriptors As IEnumerable(Of MASComponentDescriptor),
                                                       findings As IList(Of MASRenderVerificationElementCoverageClosureFinding)) As Dictionary(Of String, MASComponentDescriptor)
            Dim result As New Dictionary(Of String, MASComponentDescriptor)(StringComparer.OrdinalIgnoreCase)
            If descriptors Is Nothing Then Return result

            For Each descriptor As MASComponentDescriptor In descriptors
                If descriptor Is Nothing OrElse descriptor.ComponentType Is Nothing Then
                    findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                        "MASRenderVerification.ElementCoverage.NullDescriptor",
                        "MASComponentRegistry",
                        "A null component descriptor cannot be represented by Render Verification."))
                    Continue For
                End If

                Dim key As String = NormalizeCoverageKey(descriptor.ComponentType.FullName, descriptor.ComponentType.Name)
                If result.ContainsKey(key) Then
                    findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                        "MASRenderVerification.ElementCoverage.DuplicateDescriptor",
                        descriptor.ComponentType.Name,
                        "MASComponentRegistry contains a duplicate component descriptor key."))
                Else
                    result.Add(key, descriptor)
                End If
            Next

            Return result
        End Function

        Private Shared Function BuildDashboardRowMap(report As MASRenderVerificationElementCoverageReport,
                                                     findings As IList(Of MASRenderVerificationElementCoverageClosureFinding)) As Dictionary(Of String, MASRenderVerificationElementCoverageEntry)
            Dim result As New Dictionary(Of String, MASRenderVerificationElementCoverageEntry)(StringComparer.OrdinalIgnoreCase)
            If report Is Nothing OrElse report.Entries Is Nothing Then Return result

            For Each entry As MASRenderVerificationElementCoverageEntry In report.Entries
                If entry Is Nothing Then
                    findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                        "MASRenderVerification.ElementCoverage.NullDashboardRow",
                        "MASRenderVerificationElementCoverageBuilder",
                        "A null dashboard row cannot be displayed by Render Verification."))
                    Continue For
                End If

                Dim key As String = NormalizeCoverageKey(entry.ComponentFullName, entry.ComponentTypeName)
                If result.ContainsKey(key) Then
                    findings.Add(MASRenderVerificationElementCoverageClosureFinding.Error(
                        "MASRenderVerification.ElementCoverage.DuplicateDashboardRow",
                        entry.ComponentTypeName,
                        "MASRenderVerificationElementCoverageBuilder produced a duplicate dashboard row."))
                Else
                    result.Add(key, entry)
                End If
            Next

            Return result
        End Function

        Private Shared Function NormalizeCoverageKey(fullName As String,
                                                     typeName As String) As String
            Dim normalizedFullName As String = If(fullName, String.Empty).Trim()
            If normalizedFullName.Length > 0 Then Return normalizedFullName
            Return If(typeName, String.Empty).Trim()
        End Function

        Private NotInheritable Class ScenarioFact
            Friend Sub New(targetId As String,
                           scenarioId As String,
                           componentKey As String)
                Me.TargetId = If(targetId, String.Empty).Trim()
                Me.ScenarioId = If(scenarioId, String.Empty).Trim()
                Me.ComponentKey = If(componentKey, String.Empty).Trim()
            End Sub

            Friend ReadOnly Property TargetId As String
            Friend ReadOnly Property ScenarioId As String
            Friend ReadOnly Property ComponentKey As String
        End Class

        Private NotInheritable Class TargetScenarioIndex
            Private ReadOnly _targetIds As HashSet(Of String)
            Private ReadOnly _scenarioFacts As Dictionary(Of String, ScenarioFact)

            Private Sub New(targetIds As HashSet(Of String),
                            scenarioFacts As Dictionary(Of String, ScenarioFact))
                _targetIds = targetIds
                _scenarioFacts = scenarioFacts
            End Sub

            Friend ReadOnly Property TargetCount As Integer
                Get
                    Return _targetIds.Count
                End Get
            End Property

            Friend ReadOnly Property ScenarioCount As Integer
                Get
                    Return _scenarioFacts.Count
                End Get
            End Property

            Friend Shared Function Build(targets As IEnumerable(Of MASRenderVerificationTarget)) As TargetScenarioIndex
                Dim targetIds As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase)
                Dim scenarioFacts As New Dictionary(Of String, ScenarioFact)(StringComparer.OrdinalIgnoreCase)

                If targets IsNot Nothing Then
                    For Each target As MASRenderVerificationTarget In targets
                        If target Is Nothing Then Continue For
                        Dim targetId As String = If(target.Id, String.Empty).Trim()
                        If targetId.Length > 0 Then targetIds.Add(targetId)

                        If target.Scenarios Is Nothing Then Continue For
                        For Each scenario As MASRenderVerificationScenario In target.Scenarios
                            If scenario Is Nothing OrElse String.IsNullOrWhiteSpace(scenario.Id) Then Continue For
                            If Not scenarioFacts.ContainsKey(scenario.Id) Then
                                scenarioFacts.Add(scenario.Id, New ScenarioFact(targetId, scenario.Id, scenario.ComponentKey))
                            End If
                        Next
                    Next
                End If

                Return New TargetScenarioIndex(targetIds, scenarioFacts)
            End Function

            Friend Function ContainsTarget(targetId As String) As Boolean
                Dim normalized As String = If(targetId, String.Empty).Trim()
                Return normalized.Length > 0 AndAlso _targetIds.Contains(normalized)
            End Function

            Friend Function TryGetScenario(scenarioId As String,
                                           ByRef fact As ScenarioFact) As Boolean
                fact = Nothing
                Dim normalized As String = If(scenarioId, String.Empty).Trim()
                If normalized.Length = 0 Then Return False
                Return _scenarioFacts.TryGetValue(normalized, fact)
            End Function
        End Class

    End Class

End Namespace
