Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports Nexamas.UI.Certification

Namespace Nexamas.UI.Verification

    ''' <summary>
    ''' Blocking graph audit for RenderVerification coverage.
    ''' </summary>
    ''' <remarks>
    ''' This audit closes the gap between a syntactically valid target catalog and a trustworthy coverage graph. It compares
    ''' the official target catalog, registered capture scenes, and MASComponentRegistry coverage map, then reports exact
    ''' MissingScenes / OrphanScenes / UnusedTargets / UnmappedComponents / MissingCatalogScenario failures.
    ''' </remarks>
    Friend NotInheritable Class MASRenderVerificationCoverageAudit

        Private Sub New()
        End Sub

        Friend Shared Function BuildCurrent() As MASRenderVerificationCoverageReport
            Return Build(MASRenderVerificationTargetCatalog.CreateAllTargets(), MASQualityComponentCoverageMap.BuildCurrent())
        End Function

        Friend Shared Function Build(targets As IEnumerable(Of MASRenderVerificationTarget),
                                     componentCoverage As MASQualityComponentCoverageReport) As MASRenderVerificationCoverageReport
            Dim targetList As List(Of MASRenderVerificationTarget) = NormalizeTargets(targets)
            Dim targetIds As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase)
            Dim usedTargetIds As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase)
            Dim scenarioIds As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase)
            Dim scenarioToTarget As New Dictionary(Of String, String)(StringComparer.OrdinalIgnoreCase)
            Dim catalogComponentKeys As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase)
            Dim findings As New List(Of MASRenderVerificationCoverageFinding)()
            Dim scenarioCount As Integer = 0

            For Each target As MASRenderVerificationTarget In targetList
                If target Is Nothing Then Continue For
                If Not String.IsNullOrWhiteSpace(target.Id) Then targetIds.Add(target.Id)

                If target.Scenarios Is Nothing Then Continue For
                For Each scenario As MASRenderVerificationScenario In target.Scenarios
                    If scenario Is Nothing Then Continue For
                    scenarioCount += 1

                    If Not String.IsNullOrWhiteSpace(scenario.Id) Then
                        scenarioIds.Add(scenario.Id)
                        If Not scenarioToTarget.ContainsKey(scenario.Id) Then scenarioToTarget.Add(scenario.Id, If(target.Id, String.Empty))
                    End If

                    If Not String.IsNullOrWhiteSpace(scenario.ComponentKey) Then
                        catalogComponentKeys.Add(scenario.ComponentKey)
                        If Not MASRenderVerificationShowcaseCapture.HasRegisteredScene(scenario.ComponentKey) Then
                            findings.Add(New MASRenderVerificationCoverageFinding(
                                MASRenderVerificationCoverageFindingKind.MissingCaptureScene,
                                scenario.Id,
                                "Scenario component key '" & scenario.ComponentKey & "' has no registered capture scene."))
                        End If
                    End If
                Next
            Next

            For Each registeredKey As String In MASRenderVerificationShowcaseCapture.RegisteredSceneKeys
                If String.IsNullOrWhiteSpace(registeredKey) Then Continue For
                If Not catalogComponentKeys.Contains(registeredKey) Then
                    findings.Add(New MASRenderVerificationCoverageFinding(
                        MASRenderVerificationCoverageFindingKind.OrphanCaptureScene,
                        registeredKey,
                        "A capture scene is registered but no official target catalog scenario consumes this component key."))
                End If
            Next

            Dim coverageCount As Integer = 0
            If componentCoverage IsNot Nothing AndAlso componentCoverage.Entries IsNot Nothing Then
                For Each entry As MASQualityComponentCoverageEntry In componentCoverage.Entries
                    If entry Is Nothing Then Continue For
                    coverageCount += 1
                    AuditCoverageEntry(entry, targetIds, scenarioIds, scenarioToTarget, usedTargetIds, findings)
                Next
            Else
                findings.Add(New MASRenderVerificationCoverageFinding(
                    MASRenderVerificationCoverageFindingKind.UnmappedComponentCoverage,
                    "component-coverage-map",
                    "Component coverage report is missing, so render-verification coverage cannot be trusted."))
            End If

            For Each targetId As String In ResolveProofTargetIds(componentCoverage)
                If targetIds.Contains(targetId) Then usedTargetIds.Add(targetId)
            Next

            For Each target As MASRenderVerificationTarget In targetList
                If target Is Nothing OrElse String.IsNullOrWhiteSpace(target.Id) Then Continue For
                If Not usedTargetIds.Contains(target.Id) Then
                    findings.Add(New MASRenderVerificationCoverageFinding(
                        MASRenderVerificationCoverageFindingKind.UnusedTarget,
                        target.Id,
                        "Target is present in the official catalog but is not referenced by component coverage or a declared proof route."))
                End If
            Next

            Return New MASRenderVerificationCoverageReport(targetList.Count,
                                                           scenarioCount,
                                                           MASRenderVerificationShowcaseCapture.RegisteredSceneCount,
                                                           coverageCount,
                                                           findings)
        End Function

        Private Shared Sub AuditCoverageEntry(entry As MASQualityComponentCoverageEntry,
                                              targetIds As HashSet(Of String),
                                              scenarioIds As HashSet(Of String),
                                              scenarioToTarget As Dictionary(Of String, String),
                                              usedTargetIds As HashSet(Of String),
                                              findings As List(Of MASRenderVerificationCoverageFinding))
            If entry.HasBlockingGap Then
                findings.Add(New MASRenderVerificationCoverageFinding(
                    MASRenderVerificationCoverageFindingKind.UnmappedComponentCoverage,
                    entry.ComponentTypeName,
                    "Component coverage entry is unclassified or has incomplete evidence: " & entry.ToString()))
                Return
            End If

            If entry.CoverageKind = MASQualityComponentCoverageKind.DirectRenderScenario OrElse
               entry.CoverageKind = MASQualityComponentCoverageKind.FamilyRenderScenario Then
                If String.IsNullOrWhiteSpace(entry.TargetId) Then
                    findings.Add(New MASRenderVerificationCoverageFinding(
                        MASRenderVerificationCoverageFindingKind.MissingCatalogScenario,
                        entry.ComponentTypeName,
                        "Render-scenario coverage has no target id."))
                    Return
                End If

                If Not targetIds.Contains(entry.TargetId) Then
                    findings.Add(New MASRenderVerificationCoverageFinding(
                        MASRenderVerificationCoverageFindingKind.MissingCatalogScenario,
                        entry.ComponentTypeName,
                        "Coverage references target '" & entry.TargetId & "' but that target is not in the official catalog."))
                    Return
                End If

                usedTargetIds.Add(entry.TargetId)

                If String.IsNullOrWhiteSpace(entry.ScenarioId) Then
                    findings.Add(New MASRenderVerificationCoverageFinding(
                        MASRenderVerificationCoverageFindingKind.MissingCatalogScenario,
                        entry.ComponentTypeName,
                        "Render-scenario coverage has no scenario id."))
                    Return
                End If

                If Not scenarioIds.Contains(entry.ScenarioId) Then
                    findings.Add(New MASRenderVerificationCoverageFinding(
                        MASRenderVerificationCoverageFindingKind.MissingCatalogScenario,
                        entry.ComponentTypeName,
                        "Coverage references scenario '" & entry.ScenarioId & "' but that scenario is not in the official catalog."))
                    Return
                End If

                Dim ownerTargetId As String = String.Empty
                If scenarioToTarget.TryGetValue(entry.ScenarioId, ownerTargetId) Then
                    If Not String.Equals(ownerTargetId, entry.TargetId, StringComparison.OrdinalIgnoreCase) Then
                        findings.Add(New MASRenderVerificationCoverageFinding(
                            MASRenderVerificationCoverageFindingKind.MissingCatalogScenario,
                            entry.ComponentTypeName,
                            "Coverage references scenario '" & entry.ScenarioId & "' under target '" & entry.TargetId & "', but the catalog owns it under target '" & ownerTargetId & "'."))
                    End If
                End If
            End If
        End Sub

        Private Shared Function ResolveProofTargetIds(componentCoverage As MASQualityComponentCoverageReport) As IEnumerable(Of String)
            Dim ids As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase)

            ' Official product proof routes are intentionally part of the Render Verification catalog even when
            ' descriptor-level rows route to the generated component-descriptor target. Keeping this declaration here
            ' prevents the graph audit from treating Nexamas UI-owned baseline families as dead targets.
            ids.Add(MASRenderVerificationDescriptorScenarioPlanner.TargetId)
            ids.Add("application-shell")
            ids.Add("core-controls")
            ids.Add("surface-matrix")
            ids.Add("surface-material-proof")
            ids.Add("advanced-inputs")
            ids.Add("item-surfaces")
            ids.Add("navigation-surfaces")
            ids.Add("localization-matrix")
            ids.Add("responsive-layout-matrix")
            ids.Add("product-systems")
            ids.Add("motion-system-proof")

            If componentCoverage Is Nothing OrElse componentCoverage.Entries Is Nothing Then Return ids

            For Each entry As MASQualityComponentCoverageEntry In componentCoverage.Entries
                If entry Is Nothing OrElse entry.HasBlockingGap Then Continue For

                Select Case entry.CoverageKind
                    Case MASQualityComponentCoverageKind.SurfaceMaterialProof
                        ids.Add("surface-material-proof")
                    Case MASQualityComponentCoverageKind.SizeLayoutContractProof
                        ids.Add("responsive-layout-matrix")
                    Case MASQualityComponentCoverageKind.InteractionStateProof
                        ids.Add("core-controls")
                    Case MASQualityComponentCoverageKind.SystemInventoryProof
                        ' System inventory proof is now a read-model/table proof, not a generated PNG target.
                    Case MASQualityComponentCoverageKind.AtomicContractProof
                        ' Atomic contract proof is intentionally descriptor-only unless another gate maps it to a visual route.
                End Select
            Next

            Return ids
        End Function

        Private Shared Function NormalizeTargets(targets As IEnumerable(Of MASRenderVerificationTarget)) As List(Of MASRenderVerificationTarget)
            Dim result As New List(Of MASRenderVerificationTarget)()
            If targets Is Nothing Then Return result

            For Each target As MASRenderVerificationTarget In targets
                If target IsNot Nothing Then result.Add(target)
            Next

            Return result
        End Function

    End Class

End Namespace
