Option Strict On
Option Explicit On

Imports System
Imports System.Diagnostics
Imports System.Globalization
Imports System.IO
Imports Nexamas.UI.Application
Imports Nexamas.UI.Components
Imports Nexamas.UI.Controls
Imports Nexamas.UI.Layout
Imports Nexamas.UI.Presentation
Imports Nexamas.UI.TextInput
Imports Nexamas.UI.Values
Imports SkiaSharp


Namespace Nexamas.UI.Verification

''' <summary>
''' Nexamas UI-owned dashboard for the official Render Verification gateway.
''' Consumers attach this page through MASRenderVerification.AttachDashboard. The page consumes
''' only MASRenderVerification.CreateSnapshot and immutable read-model entries. Capture hosts,
''' target catalogs, baseline mutation, diff rules, and scene routing remain Nexamas UI-owned internals.
''' </summary>
Friend NotInheritable Class MASRenderVerificationDashboardPage
    Implements IDisposable

    Private Enum MASRenderVerificationPreviewMode
        Baseline = 0
        Current = 1
        Difference = 2
    End Enum

    Private Const ReviewQueueVisibleRowLimit As Integer = 4

    Private _window As MASApplicationWindow
    Private _title As MASTitle
    Private _description As MASLabel
    Private _status As MASLabel
    Private _statusBanner As MASStatusBanner
    Private _componentMetric As MASMetricCard
    Private _snapshotMetric As MASMetricCard
    Private _matchedMetric As MASMetricCard
    Private _failedMetric As MASMetricCard
    Private _verificationSummary As MASFormValidationSummary
    Private _reviewQueueStory As MASLabel
    Private _reviewQueueRows As MASListView
    Private _inventoryGrid As MASDataGrid
    Private _details As MASLabel
    Private _productStory As MASLabel
    Private _elementCoverageRows As MASListView
    Private _targetStory As MASLabel
    Private _previewModeSelector As MASSegmentedControl
    Private _snapshotViewer As MASImageViewer
    Private _regenerate As MASButton
    Private _previousTarget As MASButton
    Private _nextTarget As MASButton
    Private _openOutputFolder As MASButton
    Private _snapshot As MASRenderVerificationSnapshot
    Private _elementCoverage As MASRenderVerificationElementCoverageReport
    Private _inventorySnapshot As MASRenderVerificationInventorySnapshot
    Private _selectedEntryIndex As Integer = -1
    Private _selectedPreviewMode As MASRenderVerificationPreviewMode = MASRenderVerificationPreviewMode.Difference
    Private _reviewQueueReviewCount As Integer
    Private ReadOnly _outputDirectory As String
    Private ReadOnly _explicitThemeId As String
    Private _captureProgress As MASApplicationProgressHandle
    Private _suppressCoverageSelectionSync As Boolean
    Private _disposed As Boolean

    Friend Sub New(outputDirectory As String, themeId As String)
        _outputDirectory = If(outputDirectory, String.Empty)
        _explicitThemeId = If(themeId, String.Empty)
    End Sub

    Friend Sub Build(window As MASApplicationWindow)
        If window Is Nothing Then Throw New ArgumentNullException(NameOf(window))
        _window = window
        CreateControls(includeHeader:=True)
        LayoutControls()
        InitializePendingSnapshotView()
    End Sub

    Friend Sub Build(window As MASApplicationWindow, page As MASApplicationLayoutPageBuilder)
        If window Is Nothing Then Throw New ArgumentNullException(NameOf(window))
        If page Is Nothing Then Throw New ArgumentNullException(NameOf(page))
        _window = window
        CreateControls(includeHeader:=False)
        LayoutControls(page, includeHeader:=False)
        InitializePendingSnapshotView()
    End Sub

    Public Sub Dispose() Implements IDisposable.Dispose
        If _disposed Then Return
        _disposed = True

        If _regenerate IsNot Nothing Then RemoveHandler _regenerate.Click, AddressOf HandleRegenerate
        If _previousTarget IsNot Nothing Then RemoveHandler _previousTarget.Click, AddressOf HandlePreviousTarget
        If _nextTarget IsNot Nothing Then RemoveHandler _nextTarget.Click, AddressOf HandleNextTarget
        If _openOutputFolder IsNot Nothing Then RemoveHandler _openOutputFolder.Click, AddressOf HandleOpenOutputFolder
        If _reviewQueueRows IsNot Nothing Then RemoveHandler _reviewQueueRows.SelectedIndexChanged, AddressOf HandleReviewQueueSelectionChanged
        If _elementCoverageRows IsNot Nothing Then RemoveHandler _elementCoverageRows.SelectedIndexChanged, AddressOf HandleElementCoverageSelectionChanged
        If _previewModeSelector IsNot Nothing Then RemoveHandler _previewModeSelector.SelectionChanged, AddressOf HandlePreviewModeChanged

        If _captureProgress IsNot Nothing Then
            _captureProgress.Close()
            _captureProgress = Nothing
        End If

        ClearImageViewers()
        _inventorySnapshot = Nothing
        _elementCoverage = Nothing
        _snapshot = Nothing
        _window = Nothing
    End Sub

    Private Sub CreateControls(includeHeader As Boolean)
        If includeHeader Then
            _title = _window.Controls.AddTitle("Render Verification Dashboard")
            _title.WithDefaultSize()
        End If

        _description = _window.Controls.AddLabel(
            "Nexamas UI-owned PNG proof dashboard with baseline/current/diff previews. The page opens without running capture; press Start when you want to regenerate the official PNG evidence.",
            configure:=Sub(label As MASLabel)
                           label.LabelVariant = MASLabelVariant.Body
                           label.TextRole = MASLabelTextRole.Secondary
                           label.MultiLine = True
                           label.VerticalAlignment = MASVerticalAlignment.Top
                           label.WordWrap = True
                       End Sub)
        _description.WithDefaultSize()

        _status = _window.Controls.AddLabel("PNG capture: not started",
            configure:=Sub(label As MASLabel)
                           label.LabelVariant = MASLabelVariant.Heading
                           label.TextRole = MASLabelTextRole.Primary
                           label.MultiLine = True
                           label.WordWrap = True
                       End Sub)
        _status.WithDefaultSize()

        _statusBanner = _window.Controls.AddStatusBanner("PNG capture not started",
            "Component coverage is listed from MASComponentRegistry. PNG generation is started manually so opening the Showcase page stays lightweight.",
            MASToastTone.Info)
        _statusBanner.WithDefaultSize()

        _componentMetric = _window.Controls.AddMetricCard("Components", "0",
            Sub(card As MASMetricCard)
                card.WithCompact(True)
                card.WithSubtitle("registered descriptors")
                card.WithTrendVisible(False)
            End Sub)
        _componentMetric.WithDefaultSize()

        _snapshotMetric = _window.Controls.AddMetricCard("PNG snapshots", "Not run",
            Sub(card As MASMetricCard)
                card.WithCompact(True)
                card.WithSubtitle("baseline/current/diff entries")
                card.WithTrendVisible(False)
            End Sub)
        _snapshotMetric.WithDefaultSize()

        _matchedMetric = _window.Controls.AddMetricCard("Matched", "0",
            Sub(card As MASMetricCard)
                card.WithCompact(True)
                card.WithSubtitle("visual rows")
                card.WithTrendVisible(False)
                card.WithTone(MASToastTone.Success)
            End Sub)
        _matchedMetric.WithDefaultSize()

        _failedMetric = _window.Controls.AddMetricCard("Changed", "0",
            Sub(card As MASMetricCard)
                card.WithCompact(True)
                card.WithSubtitle("baseline differences")
                card.WithTrendVisible(False)
                card.WithTone(MASToastTone.Danger)
            End Sub)
        _failedMetric.WithDefaultSize()

        _verificationSummary = _window.Controls.AddFormValidationSummary("Verification summary",
            Sub(summary As MASFormValidationSummary)
                summary.AsPassiveInformation()
            End Sub)
        _verificationSummary.WithDefaultSize()

        _reviewQueueStory = _window.Controls.AddLabel("Review queue is empty until PNG capture has run.",
            configure:=Sub(label As MASLabel)
                           label.LabelVariant = MASLabelVariant.Body
                           label.TextRole = MASLabelTextRole.Secondary
                           label.MultiLine = True
                           label.VerticalAlignment = MASVerticalAlignment.Top
                           label.WordWrap = True
                       End Sub)
        _reviewQueueStory.WithDefaultSize()

        _reviewQueueRows = _window.Controls.AddListView(configure:=Sub(view As MASListView)
                                                            view.WithDetailsView()
                                                            view.StandaloneChrome()
                                                            view.AddColumn("State", 110.0F)
                                                            view.AddColumn("Component", 210.0F)
                                                            view.AddColumn("Delta", 150.0F)
                                                            view.AddColumn("Target / scenario", 330.0F)
                                                            view.AddColumn("Evidence route", 180.0F)
                                                            view.AddColumn("Meaning", 360.0F)
                                                        End Sub)
        _reviewQueueRows.WithSize(ResolveReviewQueueSurfaceSize(0))
        _reviewQueueRows.Visible = False
        AddHandler _reviewQueueRows.SelectedIndexChanged, AddressOf HandleReviewQueueSelectionChanged

        _inventoryGrid = _window.Controls.AddDataGrid(
            Sub(grid As MASDataGrid)
                grid.SelectionMode = MASDataGridSelectionMode.Cell
                ConfigureInventoryGrid(grid)
            End Sub)
        _inventoryGrid.WithDefaultSize()

        _productStory = _window.Controls.AddLabel("Render Verification waits for manual PNG capture. Component descriptor coverage is loaded without touching baseline/current/diff files.",
            configure:=Sub(label As MASLabel)
                           label.LabelVariant = MASLabelVariant.Body
                           label.TextRole = MASLabelTextRole.Secondary
                           label.MultiLine = True
                           label.VerticalAlignment = MASVerticalAlignment.Top
                           label.WordWrap = True
                       End Sub)
        _productStory.WithDefaultSize()

        _elementCoverageRows = _window.Controls.AddListView(configure:=Sub(view As MASListView)
                                                                  view.WithDetailsView()
                                                                  view.StandaloneChrome()
                                                                  view.AddColumn("Component", 210.0F)
                                                                  view.AddColumn("Coverage", 150.0F)
                                                                  view.AddColumn("State", 120.0F)
                                                                  view.AddColumn("Route", 150.0F)
                                                                  view.AddColumn("Target / scenario", 280.0F)
                                                                  view.AddColumn("Evidence", 300.0F)
                                                              End Sub)
        _elementCoverageRows.WithDefaultSize()
        AddHandler _elementCoverageRows.SelectedIndexChanged, AddressOf HandleElementCoverageSelectionChanged

        _targetStory = _window.Controls.AddLabel("Select a component row to inspect its Render Verification evidence route.",
            configure:=Sub(label As MASLabel)
                           label.LabelVariant = MASLabelVariant.Body
                           label.TextRole = MASLabelTextRole.Secondary
                           label.MultiLine = True
                           label.VerticalAlignment = MASVerticalAlignment.Top
                           label.WordWrap = True
                       End Sub)
        _targetStory.WithDefaultSize()

        _previewModeSelector = _window.Controls.AddSegmentedControl(
            New MASSegmentEntry() {
                New MASSegmentEntry("baseline", "Baseline"),
                New MASSegmentEntry("current", "Current"),
                New MASSegmentEntry("diff", "Diff")
            },
            Sub(selector As MASSegmentedControl)
                selector.WithSelectedId("diff")
            End Sub)
        _previewModeSelector.WithDefaultSize()
        AddHandler _previewModeSelector.SelectionChanged, AddressOf HandlePreviewModeChanged

        _snapshotViewer = CreateImageViewer("Snapshot evidence", "Baseline / current / diff PNG")

        _details = _window.Controls.AddLabel("The component list below is descriptor-level: it is built from MASComponentRegistry through MASQualityComponentCoverageMap and joined to Render Verification snapshot entries. No Showcase-owned or parallel component list is used.",
            configure:=Sub(label As MASLabel)
                           label.LabelVariant = MASLabelVariant.Caption
                           label.TextRole = MASLabelTextRole.Secondary
                           label.MultiLine = True
                           label.VerticalAlignment = MASVerticalAlignment.Top
                           label.WordWrap = True
                       End Sub)
        _details.WithDefaultSize()

        _regenerate = _window.Controls.AddButton("Start PNG capture")
        _regenerate.WithDefaultSize()
        AddHandler _regenerate.Click, AddressOf HandleRegenerate

        _previousTarget = _window.Controls.AddButton("Back component")
        _previousTarget.WithDefaultSize()
        AddHandler _previousTarget.Click, AddressOf HandlePreviousTarget

        _nextTarget = _window.Controls.AddButton("Next component")
        _nextTarget.WithDefaultSize()
        AddHandler _nextTarget.Click, AddressOf HandleNextTarget

        _openOutputFolder = _window.Controls.AddButton("Open output folder")
        _openOutputFolder.WithDefaultSize()
        AddHandler _openOutputFolder.Click, AddressOf HandleOpenOutputFolder
    End Sub

    Private Function CreateImageViewer(title As String, subtitle As String) As MASImageViewer
        Dim viewer As MASImageViewer = _window.Controls.AddImageViewer(title,
                                                                       subtitle,
                                                                       Sub(imageViewer As MASImageViewer)
                                                                           imageViewer.WithChromeMode(MASImageViewerChromeMode.Pinned)
                                                                           imageViewer.WithImageFitMode(MASImageViewerImageFitMode.Contain)
                                                                           imageViewer.WithFooter(True)
                                                                       End Sub)
        viewer.WithDefaultSize()
        Return viewer
    End Function

    Private Sub LayoutControls()
        _window.Controls.LayoutPage(
            Sub(page As MASApplicationLayoutPageBuilder)
                LayoutControls(page, includeHeader:=True)
            End Sub)
    End Sub

    Private Sub LayoutControls(page As MASApplicationLayoutPageBuilder, includeHeader As Boolean)
        If page Is Nothing Then Throw New ArgumentNullException(NameOf(page))

        Dim presentation As MASPresentationPageBuilder = MASPresentationPage.Begin(page, MASApplicationPageLayoutKind.Dashboard, _window.Shell)
        If includeHeader AndAlso _title IsNot Nothing Then presentation.Header(_title)

        presentation.SectionIntro(_description, _status)
        presentation.FullWidth(_statusBanner, MASSize.FillWidth)

        Dim metrics As MASApplicationLayoutGridBuilder = presentation.Grid(4, MASLayoutSpacing.Medium)
        metrics.Add(_componentMetric, MASSize.FillWidth)
        metrics.Add(_snapshotMetric, MASSize.FillWidth)
        metrics.Add(_matchedMetric, MASSize.FillWidth)
        metrics.Add(_failedMetric, MASSize.FillWidth)

        presentation.DataGridSurface(_inventoryGrid)
        presentation.FullWidth(_verificationSummary, MASSize.FillWidth)
        presentation.FullWidth(_reviewQueueStory, MASSize.FillWidth)
        presentation.FullWidthUsingControlSize(_reviewQueueRows)
        presentation.FullWidth(_productStory, MASSize.FillWidth)
        presentation.FullWidth(_targetStory, MASSize.FillWidth)

        presentation.Actions(
            Sub(actions As MASApplicationActionGroupLayoutBuilder)
                actions.Gap(MASLayoutSpacing.Medium).AlignStart().IntrinsicItemSize()
                actions.Add(_regenerate, MASSize.[Default])
                actions.Add(_previousTarget, MASSize.[Default])
                actions.Add(_nextTarget, MASSize.[Default])
                actions.Add(_openOutputFolder, MASSize.[Default])
                actions.Add(_previewModeSelector, MASSize.FillWidth)
            End Sub)

        presentation.FullWidth(_snapshotViewer, MASApplicationLayoutPolicy.ResolveControlSurfaceSize(MASApplicationControlSurfaceSizeKind.VisualProof))
        presentation.FullWidth(_details, MASSize.FillWidth)
        presentation.DataGridSurface(_elementCoverageRows)
    End Sub

    Private Sub InitializePendingSnapshotView()
        If _window Is Nothing Then Return

        Try
            _snapshot = Nothing
            _selectedEntryIndex = -1
            RefreshInventoryReadModel()
            _elementCoverage = MASRenderVerificationElementCoverageBuilder.BuildCurrent(Nothing)
            PopulateElementCoverageRows()
            PopulateReviewQueueRows()
            SelectFirstTargetIfNeeded()
            UpdateProductStory()
            UpdateDashboardSummary()
            SetStatusBanner("PNG capture not started",
                            "The page is ready. Press Start PNG capture to generate baseline/current/diff proof assets through the official MASRenderVerification gateway.",
                            MASToastTone.Info)
            SetBusy(False, "PNG capture: not started | " & If(_elementCoverage Is Nothing, "0", _elementCoverage.ComponentCount.ToString(CultureInfo.InvariantCulture)) & " components listed")
        Catch ex As Exception
            _inventorySnapshot = Nothing
            If _inventoryGrid IsNot Nothing Then
                _inventoryGrid.DataView.ClearRows()
                _inventoryGrid.DataView.Refresh()
            End If
            _elementCoverage = Nothing
            If _elementCoverageRows IsNot Nothing Then _elementCoverageRows.ClearItems()
            If _reviewQueueRows IsNot Nothing Then _reviewQueueRows.ClearItems()
            UpdateReviewQueueStory(0)
            ClearImageViewers()
            SetStatusBanner("Coverage load failed", ex.Message, MASToastTone.Danger)
            If _status IsNot Nothing Then _status.Text = "PNG capture: not started; coverage load failed"
            If _verificationSummary IsNot Nothing Then
                _verificationSummary.ClearMessages()
                _verificationSummary.AddMessage("Coverage", "Component coverage could not be loaded: " & ex.Message, MASTextValidationState.Error)
            End If
        End Try
    End Sub

    Private Sub HandleRegenerate(sender As Object, e As EventArgs)
        RefreshSnapshot("Generating PNG snapshots...")
    End Sub

    Private Sub HandlePreviousTarget(sender As Object, e As EventArgs)
        If _elementCoverageRows Is Nothing OrElse _elementCoverageRows.Items Is Nothing OrElse _elementCoverageRows.Items.Count = 0 Then Return
        Dim previousIndex As Integer = _elementCoverageRows.SelectedIndex - 1
        If previousIndex < 0 Then previousIndex = _elementCoverageRows.Items.Count - 1
        _elementCoverageRows.SelectedIndex = previousIndex
    End Sub

    Private Sub HandleNextTarget(sender As Object, e As EventArgs)
        If _elementCoverageRows Is Nothing OrElse _elementCoverageRows.Items Is Nothing OrElse _elementCoverageRows.Items.Count = 0 Then Return
        Dim nextIndex As Integer = _elementCoverageRows.SelectedIndex + 1
        If nextIndex >= _elementCoverageRows.Items.Count Then nextIndex = 0
        _elementCoverageRows.SelectedIndex = nextIndex
    End Sub

    Private Sub HandleElementCoverageSelectionChanged(sender As Object, e As EventArgs)
        If _suppressCoverageSelectionSync Then Return
        SyncReviewQueueSelectionFromCoverage()
        UpdatePreviewFromSelection()
    End Sub

    Private Sub HandleReviewQueueSelectionChanged(sender As Object, e As EventArgs)
        If _suppressCoverageSelectionSync Then Return
        If _reviewQueueRows Is Nothing Then Return

        Dim selected As MASListViewItem = _reviewQueueRows.SelectedItem
        Dim coverageEntry As MASRenderVerificationElementCoverageEntry = If(selected Is Nothing, Nothing, TryCast(selected.Tag, MASRenderVerificationElementCoverageEntry))
        If coverageEntry Is Nothing Then Return

        SelectCoverageEntry(coverageEntry)
    End Sub

    Private Sub HandlePreviewModeChanged(sender As Object, e As MASSegmentedControlSelectionChangedEventArgs)
        Dim selectedId As String = String.Empty
        If e IsNot Nothing AndAlso e.SelectedSegment IsNot Nothing Then selectedId = e.SelectedSegment.Id

        Select Case selectedId
            Case "baseline"
                _selectedPreviewMode = MASRenderVerificationPreviewMode.Baseline
            Case "current"
                _selectedPreviewMode = MASRenderVerificationPreviewMode.Current
            Case Else
                _selectedPreviewMode = MASRenderVerificationPreviewMode.Difference
        End Select

        UpdatePreviewFromSelection()
    End Sub

    Private Sub HandleOpenOutputFolder(sender As Object, e As EventArgs)
        If _snapshot Is Nothing OrElse String.IsNullOrWhiteSpace(_snapshot.OutputDirectory) Then
            _window.Services.Toasts.Warning("No Render Verification output folder is available yet. Run PNG capture first.", "Render Verification")
            Return
        End If

        Try
            Directory.CreateDirectory(_snapshot.OutputDirectory)
            Dim startInfo As New ProcessStartInfo() With {
                .FileName = _snapshot.OutputDirectory,
                .UseShellExecute = True
            }
            Process.Start(startInfo)
        Catch ex As Exception
            _window.Services.Toasts.Failure("Could not open the Render Verification output folder: " & ex.Message, "Render Verification")
        End Try
    End Sub

    Private Function ResolveSnapshotThemeId() As String
        Dim explicitThemeId As String = If(_explicitThemeId, String.Empty).Trim()
        If explicitThemeId.Length > 0 Then Return explicitThemeId

        If _window Is Nothing OrElse _window.Theme Is Nothing Then Return String.Empty

        Dim activeThemeId As String = If(_window.Theme.CurrentId, String.Empty).Trim()
        If activeThemeId.Length = 0 Then Return String.Empty

        Return activeThemeId
    End Function

    Private Sub RefreshSnapshot(statusText As String)
        If _window Is Nothing Then Return

        Dim progressHandle As MASApplicationProgressHandle = Nothing
        Try
            SetBusy(True, statusText)
            SetStatusBanner("Generating PNG snapshots", "Render Verification is capturing official Nexamas UI scenarios. The page remains a consumer of the MASRenderVerification gateway.", MASToastTone.Info)
            progressHandle = _window.Services.Progress.ShowOperation("Render Verification PNG capture", statusText, 0.0R, "MAS.RenderVerification.Dashboard")
            _captureProgress = progressHandle

            Dim progress As New DashboardProgressReporter(Me)
            _snapshot = MASRenderVerification.CreateSnapshot(outputDirectory:=_outputDirectory, progress:=progress, themeId:=ResolveSnapshotThemeId())
            If progressHandle IsNot Nothing Then progressHandle.Update("Building component coverage read model...", 98.0R)
            RefreshInventoryReadModel()
            _elementCoverage = MASRenderVerificationElementCoverageBuilder.BuildCurrent(_snapshot)
            PopulateElementCoverageRows()
            PopulateReviewQueueRows()
            UpdateProductStory()
            SelectFirstTargetIfNeeded()
            UpdateDashboardSummary()

            Dim finalStatus As String = "Visual proof: " & ResolveSnapshotStateText(_snapshot) & "  |  " & BuildSnapshotCountsText(_snapshot) & " | " & If(_elementCoverage Is Nothing, "0", _elementCoverage.ComponentCount.ToString(CultureInfo.InvariantCulture)) & " components listed"
            SetBusy(False, finalStatus)
            _regenerate.Text = "Regenerate PNG snapshots"
            SetStatusBannerFromSnapshot()
            _window.Services.Toasts.Success("Render Verification PNG snapshot is ready.", "Render Verification")
        Catch ex As Exception
            _snapshot = Nothing
            _selectedEntryIndex = -1
            Try
                RefreshInventoryReadModel()
                _elementCoverage = MASRenderVerificationElementCoverageBuilder.BuildCurrent(Nothing)
                PopulateElementCoverageRows()
                PopulateReviewQueueRows()
                SelectFirstTargetIfNeeded()
                UpdateDashboardSummary()
            Catch coverageException As Exception
                _elementCoverage = Nothing
                If _elementCoverageRows IsNot Nothing Then _elementCoverageRows.ClearItems()
                If _reviewQueueRows IsNot Nothing Then _reviewQueueRows.ClearItems()
                UpdateReviewQueueStory(0)
                If _inventoryGrid IsNot Nothing Then
                    _inventoryGrid.DataView.ClearRows()
                    _inventoryGrid.DataView.Refresh()
                End If
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDiagnosticOnly(coverageException, "RenderVerificationDashboard.ComponentCoverageFallback")
            End Try
            ClearImageViewers()
            If _status IsNot Nothing Then _status.Text = "Snapshot: failed"
            SetStatusBanner("PNG capture failed", ex.Message, MASToastTone.Danger)
            If _details IsNot Nothing Then _details.Text = "The host called MASRenderVerification.AttachDashboard only; capture internals remain inside Nexamas.UI. Component rows, when available, still come from MASComponentRegistry and MASQualityComponentCoverageMap."
            _window.Services.Toasts.Failure("Render Verification snapshot failed: " & ex.Message, "Render Verification")
        Finally
            _captureProgress = Nothing
            If progressHandle IsNot Nothing Then progressHandle.Close()
            SetBusy(False, Nothing)
        End Try
    End Sub

    Private Sub SetBusy(isBusy As Boolean, statusText As String)
        If _regenerate IsNot Nothing Then _regenerate.Enabled = Not isBusy
        If _previousTarget IsNot Nothing Then _previousTarget.Enabled = Not isBusy
        If _nextTarget IsNot Nothing Then _nextTarget.Enabled = Not isBusy
        If _openOutputFolder IsNot Nothing Then _openOutputFolder.Enabled = Not isBusy
        If _previewModeSelector IsNot Nothing Then _previewModeSelector.Enabled = Not isBusy
        If Not String.IsNullOrWhiteSpace(statusText) AndAlso _status IsNot Nothing Then _status.Text = statusText
    End Sub

    Private Sub SetStatusBanner(title As String, message As String, tone As MASToastTone)
        If _statusBanner Is Nothing Then Return
        _statusBanner.Title = If(title, String.Empty)
        _statusBanner.Message = If(message, String.Empty)
        _statusBanner.Tone = tone
    End Sub

    Private Sub SetStatusBannerFromSnapshot()
        If _snapshot Is Nothing Then
            SetStatusBanner("PNG capture not started", "Press Start PNG capture to generate the official baseline/current/diff evidence.", MASToastTone.Info)
            Return
        End If

        Dim message As String = BuildSnapshotCountsText(_snapshot) & ". Changed rows are baseline differences, not missing component images."
        If _snapshot.FailedCount > 0 OrElse _snapshot.InvalidCount > 0 Then
            SetStatusBanner("Visual proof FAILED", message, MASToastTone.Danger)
        ElseIf _snapshot.RequiresApproval Then
            SetStatusBanner("Visual proof needs review", message, MASToastTone.Warning)
        Else
            SetStatusBanner("Visual proof PASSED", message, MASToastTone.Success)
        End If
    End Sub

    Private Sub PopulateElementCoverageRows()
        If _elementCoverageRows Is Nothing Then Return

        _elementCoverageRows.ClearItems()
        If _elementCoverage Is Nothing OrElse _elementCoverage.Entries Is Nothing Then Return

        For Each coverageEntry As MASRenderVerificationElementCoverageEntry In _elementCoverage.Entries
            If coverageEntry Is Nothing Then Continue For

            Dim routeText As String = If(String.IsNullOrWhiteSpace(coverageEntry.TargetId),
                                         If(String.IsNullOrWhiteSpace(coverageEntry.ScenarioId), "contract-only", coverageEntry.ScenarioId),
                                         coverageEntry.TargetId & If(String.IsNullOrWhiteSpace(coverageEntry.ScenarioId), String.Empty, " / " & coverageEntry.ScenarioId))
            Dim evidenceText As String = If(String.IsNullOrWhiteSpace(coverageEntry.EvidenceKey), "<none>", coverageEntry.EvidenceKey)
            Dim item As MASListViewItem = MASListViewItem.Create(coverageEntry.ComponentTypeName).WithSubItems(New String() {
                coverageEntry.CoverageKind.ToString(),
                coverageEntry.StateText,
                coverageEntry.EvidenceRouteText,
                routeText,
                evidenceText
            })
            item.Tag = coverageEntry
            _elementCoverageRows.AddItem(item)
        Next
    End Sub

    Private Sub PopulateReviewQueueRows()
        If _reviewQueueRows Is Nothing Then Return

        _reviewQueueRows.ClearItems()

        If _snapshot Is Nothing Then
            ApplyReviewQueueLayoutState(0)
            UpdateReviewQueueStory(0)
            Return
        End If

        If _elementCoverage Is Nothing OrElse _elementCoverage.Entries Is Nothing Then
            ApplyReviewQueueLayoutState(0)
            UpdateReviewQueueStory(0)
            Return
        End If

        Dim reviewCount As Integer = 0
        For Each coverageEntry As MASRenderVerificationElementCoverageEntry In _elementCoverage.Entries
            If coverageEntry Is Nothing OrElse Not IsReviewQueueEntry(coverageEntry) Then Continue For

            Dim entry As MASRenderVerificationEntry = coverageEntry.SnapshotEntry
            Dim targetText As String = If(String.IsNullOrWhiteSpace(coverageEntry.TargetId), "<contract>", coverageEntry.TargetId)
            If Not String.IsNullOrWhiteSpace(coverageEntry.ScenarioId) Then targetText &= " / " & coverageEntry.ScenarioId

            Dim item As MASListViewItem = MASListViewItem.Create(coverageEntry.StateText).WithSubItems(New String() {
                coverageEntry.ComponentTypeName,
                If(entry Is Nothing, "No PNG row", FormatDelta(entry)),
                targetText,
                If(String.IsNullOrWhiteSpace(coverageEntry.EvidenceRouteText), "<unknown>", coverageEntry.EvidenceRouteText),
                ResolveReviewQueueMeaning(coverageEntry)
            })
            item.Tag = coverageEntry
            _reviewQueueRows.AddItem(item)
            reviewCount += 1
        Next

        ApplyReviewQueueLayoutState(reviewCount)
        UpdateReviewQueueStory(reviewCount)
    End Sub

    Private Sub UpdateReviewQueueStory(reviewCount As Integer)
        If _reviewQueueStory Is Nothing Then Return

        If _snapshot Is Nothing Then
            _reviewQueueStory.Text = "Review queue" & Environment.NewLine &
                "No visual review rows are available yet. Run PNG capture when you want to compare current proof assets with the approved baseline."
            Return
        End If

        If reviewCount > 0 Then
            _reviewQueueStory.Text = "Review queue" & Environment.NewLine &
                reviewCount.ToString(CultureInfo.InvariantCulture) & " row(s) need visual review. The queue shows up to " & ReviewQueueVisibleRowLimit.ToString(CultureInfo.InvariantCulture) & " row(s) and scrolls for the rest; selecting a row jumps to its baseline/current/diff evidence."
            Return
        End If

        _reviewQueueStory.Text = "Review queue" & Environment.NewLine &
            "No rows need visual review. The current snapshot rows are clean against the approved baseline."
    End Sub

    Private Sub ApplyReviewQueueLayoutState(reviewCount As Integer)
        _reviewQueueReviewCount = Math.Max(0, reviewCount)

        If _reviewQueueRows Is Nothing Then Return

        _reviewQueueRows.WithSize(ResolveReviewQueueSurfaceSize(_reviewQueueReviewCount))
        _reviewQueueRows.Visible = (_reviewQueueReviewCount > 0)
    End Sub

    Private Shared Function ResolveReviewQueueSurfaceSize(reviewCount As Integer) As MASSize
        If reviewCount <= 0 Then Return MASSize.HugContent

        Dim visibleRows As Integer = Math.Min(ReviewQueueVisibleRowLimit, Math.Max(1, reviewCount))
        Dim rowHeight As Single = CollectionTokens.RowHeightDip(MASListTreeIconSizeMode.Large)
        Dim headerHeight As Single = CollectionTokens.HeaderHeightDip
        Dim contentPadding As Single = CollectionTokens.SurfaceInnerPaddingDip * 2.0F
        Dim targetHeight As Single = headerHeight + (rowHeight * CSng(visibleRows)) + contentPadding
        targetHeight = Math.Max(MASIntrinsicControlSizeMetrics.ResolveListViewMinimumSize().Height, targetHeight)

        Dim intrinsicHeight As Single = MASIntrinsicControlSizeMetrics.ResolveListViewPreferredViewportHeight(
            itemCount:=Math.Max(1, reviewCount),
            rowHeightDip:=rowHeight,
            includesHeader:=True,
            headerHeightDip:=headerHeight)

        Return MASSize.FillWidth.OffsetHeight(targetHeight - intrinsicHeight)
    End Function

    Private Shared Function IsReviewQueueEntry(coverageEntry As MASRenderVerificationElementCoverageEntry) As Boolean
        If coverageEntry Is Nothing Then Return False
        Select Case coverageEntry.State
            Case MASRenderVerificationElementCoverageState.Changed,
                 MASRenderVerificationElementCoverageState.Invalid,
                 MASRenderVerificationElementCoverageState.VisualReview,
                 MASRenderVerificationElementCoverageState.MissingSnapshot,
                 MASRenderVerificationElementCoverageState.Unclassified
                Return True
            Case Else
                Return False
        End Select
    End Function

    Private Shared Function ResolveReviewQueueMeaning(coverageEntry As MASRenderVerificationElementCoverageEntry) As String
        If coverageEntry Is Nothing Then Return String.Empty

        Select Case coverageEntry.State
            Case MASRenderVerificationElementCoverageState.Changed
                Return "Current PNG differs from an approved baseline. Review the diff: approve the new baseline only if the visual change is intentional."
            Case MASRenderVerificationElementCoverageState.Invalid
                Return "The snapshot row is invalid and should be treated as a blocking Render Verification problem."
            Case MASRenderVerificationElementCoverageState.VisualReview
                Return "A new baseline was seeded and needs human approval before the run can become a clean pass."
            Case MASRenderVerificationElementCoverageState.MissingSnapshot
                Return "The component descriptor expects a visual capture, but no snapshot row was joined."
            Case MASRenderVerificationElementCoverageState.Unclassified
                Return "The component descriptor is not classified into a trusted proof route."
            Case Else
                Return coverageEntry.Summary
        End Select
    End Function

    Private Sub SelectCoverageEntry(coverageEntry As MASRenderVerificationElementCoverageEntry)
        If coverageEntry Is Nothing OrElse _elementCoverageRows Is Nothing OrElse _elementCoverageRows.Items Is Nothing Then Return

        For index As Integer = 0 To _elementCoverageRows.Items.Count - 1
            Dim item As MASListViewItem = _elementCoverageRows.Items(index)
            If item Is Nothing Then Continue For
            If Object.ReferenceEquals(TryCast(item.Tag, MASRenderVerificationElementCoverageEntry), coverageEntry) Then
                _suppressCoverageSelectionSync = True
                Try
                    _elementCoverageRows.SelectedIndex = index
                Finally
                    _suppressCoverageSelectionSync = False
                End Try
                UpdatePreviewFromSelection()
                Return
            End If
        Next
    End Sub

    Private Sub SyncReviewQueueSelectionFromCoverage()
        If _reviewQueueRows Is Nothing OrElse _reviewQueueRows.Items Is Nothing OrElse _elementCoverageRows Is Nothing Then Return

        Dim selectedCoverageItem As MASListViewItem = _elementCoverageRows.SelectedItem
        Dim selectedCoverageEntry As MASRenderVerificationElementCoverageEntry = If(selectedCoverageItem Is Nothing, Nothing, TryCast(selectedCoverageItem.Tag, MASRenderVerificationElementCoverageEntry))
        If selectedCoverageEntry Is Nothing Then Return

        Dim reviewIndex As Integer = -1
        For index As Integer = 0 To _reviewQueueRows.Items.Count - 1
            Dim reviewItem As MASListViewItem = _reviewQueueRows.Items(index)
            If reviewItem Is Nothing Then Continue For
            If Object.ReferenceEquals(TryCast(reviewItem.Tag, MASRenderVerificationElementCoverageEntry), selectedCoverageEntry) Then
                reviewIndex = index
                Exit For
            End If
        Next

        If reviewIndex < 0 OrElse _reviewQueueRows.SelectedIndex = reviewIndex Then Return

        _suppressCoverageSelectionSync = True
        Try
            _reviewQueueRows.SelectedIndex = reviewIndex
        Finally
            _suppressCoverageSelectionSync = False
        End Try
    End Sub

    Private Function FindFirstReviewQueueCoverageRowIndex() As Integer
        If _elementCoverageRows Is Nothing OrElse _elementCoverageRows.Items Is Nothing Then Return -1

        For index As Integer = 0 To _elementCoverageRows.Items.Count - 1
            Dim item As MASListViewItem = _elementCoverageRows.Items(index)
            Dim coverageEntry As MASRenderVerificationElementCoverageEntry = If(item Is Nothing, Nothing, TryCast(item.Tag, MASRenderVerificationElementCoverageEntry))
            If IsReviewQueueEntry(coverageEntry) Then Return index
        Next

        Return -1
    End Function

    Private Sub ConfigureInventoryGrid(grid As MASDataGrid)
        If grid Is Nothing Then Return
        Dim view As MASDataView = grid.DataView
        view.ClearRows()
        view.ClearColumns()

        Dim areaColumn As MASDataGridColumn = view.AddColumn("area", "Area")
        areaColumn.WidthDip = 180.0F
        areaColumn.IsReadOnly = True

        Dim metricColumn As MASDataGridColumn = view.AddColumn("metric", "Metric")
        metricColumn.WidthDip = 170.0F
        metricColumn.IsReadOnly = True

        Dim valueColumn As MASDataGridColumn = view.AddColumn("value", "Value")
        valueColumn.WidthDip = 140.0F
        valueColumn.IsReadOnly = True
        valueColumn.TextAlignment = MASHorizontalAlignment.Right

        Dim stateColumn As MASDataGridColumn = view.AddColumn("state", "State")
        stateColumn.WidthDip = 120.0F
        stateColumn.IsReadOnly = True

        Dim sourceColumn As MASDataGridColumn = view.AddColumn("source", "Source")
        sourceColumn.WidthDip = 330.0F
        sourceColumn.IsReadOnly = True
        view.Refresh()
    End Sub

    Private Sub RefreshInventoryReadModel()
        If _inventoryGrid Is Nothing Then Return

        _inventorySnapshot = MASRenderVerificationInventorySnapshotBuilder.Build()
        Dim view As MASDataView = _inventoryGrid.DataView
        view.ClearRows()
        If _inventorySnapshot Is Nothing Then
            view.AddRow("System inventory", "Status", "unavailable", "Review", "MASRenderVerificationInventorySnapshotBuilder")
            view.Refresh()
            Return
        End If

        If _inventorySnapshot.Facts IsNot Nothing Then
            For Each fact As MASRenderVerificationInventoryFact In _inventorySnapshot.Facts
                If fact Is Nothing Then Continue For
                view.AddRow(fact.Category,
                            fact.Name,
                            fact.Value,
                            fact.State,
                            ResolveInventoryFactSource(fact.Category))
            Next
        End If

        If view.Rows.Count = 0 AndAlso _inventorySnapshot.Metrics IsNot Nothing Then
            For Each metric As MASRenderVerificationInventoryMetric In _inventorySnapshot.Metrics
                If metric Is Nothing Then Continue For
                view.AddRow(metric.Name,
                            "Summary",
                            metric.Value,
                            metric.State,
                            metric.Boundary)
            Next
        End If

        view.Refresh()
    End Sub

    Private Shared Function ResolveInventoryFactSource(category As String) As String
        Dim safeCategory As String = If(category, String.Empty).Trim()
        Select Case safeCategory
            Case "Theme Studio"
                Return "MASThemeStudio.CreateSnapshot"
            Case "Render Verification"
                Return "TargetCatalog + SceneRegistry"
            Case "Localization / RTL"
                Return "MASLocalization.CreateSnapshot"
            Case "Motion + Policy"
                Return "MASMotionSystem + Render policy"
            Case Else
                Return "MASRenderVerificationInventorySnapshotBuilder"
        End Select
    End Function

    Private Sub UpdateProductStory()
        If _productStory Is Nothing Then Return
        If _snapshot Is Nothing Then
            _productStory.Text = "Product proof" & Environment.NewLine & Environment.NewLine &
                "PNG capture has not run during this page visit. Component descriptor coverage and the system inventory table are already available from Nexamas UI-owned read models, but baseline/current/diff previews stay empty until Start PNG capture is pressed."
            Return
        End If

        Dim reviewState As String = ResolveSnapshotStateText(_snapshot)
        Dim componentText As String = If(_elementCoverage Is Nothing,
                                         "Component coverage is not available yet.",
                                         _elementCoverage.Summary)
        _productStory.Text = "Product proof" & Environment.NewLine & Environment.NewLine &
            "The official snapshot is " & reviewState & ". Baseline, current, and diff PNG assets stay Nexamas UI-owned, while this dashboard presents readable evidence and system inventory through Nexamas UI read models instead of a readable PNG dashboard card." & Environment.NewLine & Environment.NewLine &
            "Component coverage" & Environment.NewLine & componentText
    End Sub

    Private Sub SelectFirstTargetIfNeeded()
        If _elementCoverageRows Is Nothing OrElse _elementCoverageRows.Items Is Nothing OrElse _elementCoverageRows.Items.Count = 0 Then
            _selectedEntryIndex = -1
            ClearImageViewers()
            If _targetStory IsNot Nothing Then _targetStory.Text = "No registered component coverage row is available for preview."
            Return
        End If

        If _snapshot IsNot Nothing Then
            Dim reviewIndex As Integer = FindFirstReviewQueueCoverageRowIndex()
            If reviewIndex >= 0 Then
                _elementCoverageRows.SelectedIndex = reviewIndex
                SyncReviewQueueSelectionFromCoverage()
                Return
            End If
        End If

        If _elementCoverageRows.SelectedIndex < 0 Then
            _elementCoverageRows.SelectedIndex = 0
        Else
            SyncReviewQueueSelectionFromCoverage()
            UpdatePreviewFromSelection()
        End If
    End Sub

    Private Sub UpdateDashboardSummary()
        UpdateMetricCards()
        UpdateVerificationSummary()
        SetStatusBannerFromSnapshot()
    End Sub

    Private Sub UpdateMetricCards()
        Dim componentCount As Integer = If(_elementCoverage Is Nothing, 0, _elementCoverage.ComponentCount)
        Dim visualRows As Integer = If(_snapshot Is Nothing, 0, _snapshot.EntryCount)
        Dim matchedRows As Integer = If(_snapshot Is Nothing, 0, _snapshot.PassedCount)
        Dim failedRows As Integer = If(_snapshot Is Nothing, 0, _snapshot.FailedCount + _snapshot.InvalidCount)
        Dim changedRows As Integer = If(_elementCoverage Is Nothing, 0, _elementCoverage.ChangedCount)

        If _componentMetric IsNot Nothing Then
            _componentMetric.SetMetric("Components",
                                       componentCount.ToString(CultureInfo.InvariantCulture),
                                       "registered descriptors",
                                       If(_elementCoverage Is Nothing, String.Empty, _elementCoverage.BlockingCount.ToString(CultureInfo.InvariantCulture) & " blocking"),
                                       If(_elementCoverage IsNot Nothing AndAlso _elementCoverage.BlockingCount > 0, MASToastTone.Warning, MASToastTone.Info))
        End If

        If _snapshotMetric IsNot Nothing Then
            _snapshotMetric.SetMetric("PNG snapshots",
                                      If(_snapshot Is Nothing, "Not run", visualRows.ToString(CultureInfo.InvariantCulture)),
                                      "baseline/current/diff entries",
                                      If(_snapshot Is Nothing, "manual start", ResolveSnapshotStateText(_snapshot)),
                                      If(_snapshot Is Nothing, MASToastTone.Info, ResolveSnapshotTone(_snapshot)))
        End If

        If _matchedMetric IsNot Nothing Then
            _matchedMetric.SetMetric("Matched",
                                     matchedRows.ToString(CultureInfo.InvariantCulture),
                                     "visual rows",
                                     If(_snapshot Is Nothing, "pending", "passed"),
                                     MASToastTone.Success)
        End If

        If _failedMetric IsNot Nothing Then
            _failedMetric.SetMetric("Changed",
                                    failedRows.ToString(CultureInfo.InvariantCulture),
                                    "baseline differences",
                                    changedRows.ToString(CultureInfo.InvariantCulture) & " changed components",
                                    If(failedRows > 0 OrElse changedRows > 0, MASToastTone.Danger, MASToastTone.Success))
        End If
    End Sub

    Private Sub UpdateVerificationSummary()
        If _verificationSummary Is Nothing Then Return
        _verificationSummary.ClearMessages()

        If _snapshot Is Nothing Then
            _verificationSummary.AddMessage("Capture", "PNG generation is intentionally manual. Press Start PNG capture when you want to regenerate the official proof assets.", MASTextValidationState.Information)
        ElseIf _snapshot.FailedCount > 0 OrElse _snapshot.InvalidCount > 0 Then
            _verificationSummary.AddMessage("Snapshot", "Visual proof changed against existing baselines: " & _snapshot.FailedCount.ToString(CultureInfo.InvariantCulture) & " changed and " & _snapshot.InvalidCount.ToString(CultureInfo.InvariantCulture) & " invalid entries. This is a baseline-review error, not missing component coverage; the Review queue lists the affected rows.", MASTextValidationState.Error)
        ElseIf _snapshot.RequiresApproval Then
            _verificationSummary.AddMessage("Snapshot", "Visual proof generated " & _snapshot.SeededCount.ToString(CultureInfo.InvariantCulture) & " new baseline item(s); approval/review is required before this becomes a clean pass.", MASTextValidationState.Warning)
        Else
            _verificationSummary.AddMessage("Snapshot", "Visual proof passed against the approved baseline.", MASTextValidationState.Success)
        End If

        If _elementCoverage Is Nothing Then
            _verificationSummary.AddMessage("Coverage", "Component descriptor coverage is unavailable.", MASTextValidationState.Warning)
        Else
            Dim coverageMessage As String = _elementCoverage.ComponentCount.ToString(CultureInfo.InvariantCulture) & " component descriptor(s), " &
                                            _elementCoverage.MissingSnapshotCount.ToString(CultureInfo.InvariantCulture) & " missing snapshot row(s), " &
                                            _elementCoverage.UnclassifiedCount.ToString(CultureInfo.InvariantCulture) & " unclassified row(s)."
            Dim coverageState As MASTextValidationState = If(_elementCoverage.BlockingCount > 0, MASTextValidationState.Warning, MASTextValidationState.Success)
            If _snapshot Is Nothing AndAlso _elementCoverage.MissingSnapshotCount > 0 Then coverageState = MASTextValidationState.Information
            _verificationSummary.AddMessage("Coverage", coverageMessage, coverageState)

            If _elementCoverage.GraphReport IsNot Nothing Then
                If _elementCoverage.GraphReport.IsComplete Then
                    _verificationSummary.AddMessage("Coverage graph", "Coverage graph is complete.", MASTextValidationState.Success)
                Else
                    _verificationSummary.AddMessage("Coverage graph", "Coverage graph has " & _elementCoverage.GraphReport.FindingCount.ToString(CultureInfo.InvariantCulture) & " finding(s).", MASTextValidationState.Warning)
                End If
            End If
        End If
    End Sub

    Private Sub UpdatePreviewFromSelection()
        If _elementCoverageRows Is Nothing Then Return

        Dim selected As MASListViewItem = _elementCoverageRows.SelectedItem
        Dim coverageEntry As MASRenderVerificationElementCoverageEntry = If(selected Is Nothing, Nothing, TryCast(selected.Tag, MASRenderVerificationElementCoverageEntry))
        If coverageEntry Is Nothing Then
            ClearImageViewers()
            If _targetStory IsNot Nothing Then _targetStory.Text = "Select a registered component row to inspect its Render Verification coverage."
            Return
        End If

        Dim entry As MASRenderVerificationEntry = coverageEntry.SnapshotEntry
        If entry Is Nothing Then
            ClearImageViewers()
            If _snapshotViewer IsNot Nothing Then
                _snapshotViewer.Title = "Snapshot evidence"
                _snapshotViewer.Subtitle = If(_snapshot Is Nothing,
                                              "PNG capture has not run yet for this page visit.",
                                              "No PNG preview is attached to this descriptor row.")
            End If
        Else
            Select Case _selectedPreviewMode
                Case MASRenderVerificationPreviewMode.Baseline
                    SetViewerBitmap(_snapshotViewer,
                                    If(entry.HasBaselineImage, entry.CreateBaselineBitmap(), Nothing),
                                    "Approved baseline",
                                    entry.TargetName,
                                    "Baseline PNG is not available for this scenario.")
                Case MASRenderVerificationPreviewMode.Current
                    SetViewerBitmap(_snapshotViewer,
                                    If(entry.HasCurrentImage, entry.CreateCurrentBitmap(), Nothing),
                                    "Current capture",
                                    entry.DisplayName,
                                    "Current PNG is not available for this scenario.")
                Case Else
                    If entry.HasDifferenceImage Then
                        SetViewerBitmap(_snapshotViewer,
                                        entry.CreateDifferenceBitmap(),
                                        "Visual difference",
                                        FormatDelta(entry),
                                        "Visual diff PNG is not available for this scenario.")
                    ElseIf entry.HasCurrentImage Then
                        Dim fallbackText As String = If(entry.DifferentPixelCount = 0,
                                                        "No diff PNG was needed — showing the complete current capture.",
                                                        "Diff PNG is not available — showing the complete current capture.")
                        SetViewerBitmap(_snapshotViewer,
                                        entry.CreateCurrentBitmap(),
                                        "Visual difference",
                                        fallbackText,
                                        "Current PNG is not available for this scenario.")
                    Else
                        Dim diffEmptyText As String = If(entry.DifferentPixelCount = 0,
                                                         "No visual difference — 0 changed pixels",
                                                         "Visual diff PNG is not available for this scenario.")
                        SetViewerBitmap(_snapshotViewer,
                                        Nothing,
                                        "Visual difference",
                                        FormatDelta(entry),
                                        diffEmptyText)
                    End If
            End Select
        End If

        Dim rowIndexText As String = String.Empty
        If _elementCoverageRows.SelectedIndex >= 0 AndAlso _elementCoverage IsNot Nothing Then
            rowIndexText = "Selected component " & (_elementCoverageRows.SelectedIndex + 1).ToString(CultureInfo.InvariantCulture) & " of " & _elementCoverage.ComponentCount.ToString(CultureInfo.InvariantCulture) & Environment.NewLine & Environment.NewLine
        End If

        Dim targetText As String = rowIndexText &
            "Component: " & coverageEntry.ComponentTypeName & Environment.NewLine &
            "Category: " & coverageEntry.CategoryName & Environment.NewLine &
            "Coverage: " & coverageEntry.CoverageKind.ToString() & "  |  State: " & coverageEntry.StateText & Environment.NewLine &
            "Evidence route: " & If(String.IsNullOrWhiteSpace(coverageEntry.EvidenceRouteText), "<unknown>", coverageEntry.EvidenceRouteText) & Environment.NewLine &
            "Evidence: " & If(String.IsNullOrWhiteSpace(coverageEntry.EvidenceKey), "<none>", coverageEntry.EvidenceKey) & Environment.NewLine &
            "Target: " & If(String.IsNullOrWhiteSpace(coverageEntry.TargetId), "<contract-only>", coverageEntry.TargetId) & Environment.NewLine &
            "Scenario: " & If(String.IsNullOrWhiteSpace(coverageEntry.ScenarioId), "<contract-only>", coverageEntry.ScenarioId)

        If entry IsNot Nothing Then
            targetText &= Environment.NewLine &
                "Canvas: " & entry.WidthPx.ToString(CultureInfo.InvariantCulture) & "x" & entry.HeightPx.ToString(CultureInfo.InvariantCulture) & " px" & Environment.NewLine &
                "Review state: " & ResolveEntryVisualState(entry) & "  |  Delta: " & FormatDelta(entry)
        End If

        targetText &= Environment.NewLine & Environment.NewLine & coverageEntry.Summary

        If _targetStory IsNot Nothing Then _targetStory.Text = targetText
        If _details IsNot Nothing Then _details.Text = "The component list below is descriptor-level: it is built from MASComponentRegistry through MASQualityComponentCoverageMap, then joined to Render Verification snapshot entries and the coverage graph audit. No Showcase-owned or parallel component list is used."
    End Sub

    Private Shared Sub SetViewerBitmap(viewer As MASImageViewer,
                                      bitmap As SKBitmap,
                                      title As String,
                                      subtitle As String,
                                      emptyText As String)
        If viewer Is Nothing Then
            If bitmap IsNot Nothing Then bitmap.Dispose()
            Return
        End If

        If bitmap Is Nothing Then
            viewer.ClearImage()
            viewer.Title = title
            viewer.Subtitle = emptyText
            Return
        End If

        viewer.SetImage(bitmap, title, subtitle, resetView:=True, takeOwnership:=True)
    End Sub

    Private Sub ClearImageViewers()
        ClearImageViewer(_snapshotViewer, "Snapshot evidence", "Select a Render Verification target to preview PNG assets")
    End Sub

    Private Shared Sub ClearImageViewer(viewer As MASImageViewer, title As String, subtitle As String)
        If viewer Is Nothing Then Return
        viewer.ClearImage()
        viewer.Title = title
        viewer.Subtitle = subtitle
    End Sub

    Private Shared Function BuildSnapshotCountsText(snapshot As MASRenderVerificationSnapshot) As String
        If snapshot Is Nothing Then Return "0/0 snapshots matched"
        Return snapshot.PassedCount.ToString(CultureInfo.InvariantCulture) & "/" & snapshot.EntryCount.ToString(CultureInfo.InvariantCulture) &
            " snapshots matched; new baselines waiting review: " & snapshot.SeededCount.ToString(CultureInfo.InvariantCulture) &
            "; changed against baseline: " & snapshot.FailedCount.ToString(CultureInfo.InvariantCulture) &
            "; invalid: " & snapshot.InvalidCount.ToString(CultureInfo.InvariantCulture)
    End Function

    Private Shared Function ResolveSnapshotStateText(snapshot As MASRenderVerificationSnapshot) As String
        If snapshot Is Nothing Then Return "Unavailable"
        If snapshot.FailedCount > 0 OrElse snapshot.InvalidCount > 0 Then Return "FAILED"
        If snapshot.RequiresApproval Then Return "VISUAL REVIEW"
        If snapshot.Passed Then Return "PASSED"
        Return "VISUAL REVIEW"
    End Function

    Private Shared Function ResolveSnapshotTone(snapshot As MASRenderVerificationSnapshot) As MASToastTone
        If snapshot Is Nothing Then Return MASToastTone.Info
        If snapshot.FailedCount > 0 OrElse snapshot.InvalidCount > 0 Then Return MASToastTone.Danger
        If snapshot.RequiresApproval Then Return MASToastTone.Warning
        If snapshot.Passed Then Return MASToastTone.Success
        Return MASToastTone.Warning
    End Function

    Private Shared Function ResolveEntryVisualState(entry As MASRenderVerificationEntry) As String
        If entry Is Nothing Then Return "Unknown"
        Select Case entry.Outcome
            Case MASRenderVerificationOutcome.Passed
                Return "Matched"
            Case MASRenderVerificationOutcome.BaselineSeeded
                Return "Review"
            Case MASRenderVerificationOutcome.Failed
                Return "Changed"
            Case Else
                Return "Invalid"
        End Select
    End Function

    Private Shared Function FormatDelta(entry As MASRenderVerificationEntry) As String
        If entry Is Nothing Then Return "0 px / 0.00000%"
        Return entry.DifferentPixelCount.ToString(CultureInfo.InvariantCulture) & " px / " & entry.DifferenceRatio.ToString("0.00000%", CultureInfo.InvariantCulture)
    End Function

    Private Sub ReportProgress(info As MASRenderVerificationProgressInfo)
        If info Is Nothing Then Return

        If _captureProgress IsNot Nothing Then
            _captureProgress.Update(If(String.IsNullOrWhiteSpace(info.StatusText), "Generating PNG snapshots...", info.StatusText), info.Percent)
        End If
    End Sub

    Private NotInheritable Class DashboardProgressReporter
        Implements IProgress(Of MASRenderVerificationProgressInfo)

        Private ReadOnly _owner As MASRenderVerificationDashboardPage

        Friend Sub New(owner As MASRenderVerificationDashboardPage)
            _owner = owner
        End Sub

        Public Sub Report(value As MASRenderVerificationProgressInfo) Implements IProgress(Of MASRenderVerificationProgressInfo).Report
            If _owner IsNot Nothing Then _owner.ReportProgress(value)
        End Sub
    End Class
End Class


End Namespace
