Option Strict On
Option Explicit On

Imports System
Imports System.ComponentModel
Imports System.Collections.Generic
Imports System.IO
Imports System.Linq
Imports Nexamas.UI.Application
Imports Nexamas.UI.Layout

Namespace Nexamas.UI.Verification

    ''' <summary>
    ''' Official release-evidence gateway for Render Verification product proof inspection and dashboard attachment.
    ''' </summary>
    ''' <remarks>
    ''' The gateway intentionally exposes immutable product proof snapshots and a one-call dashboard attachment only. It does not expose
    ''' capture handlers, runner execution, baseline-store mutation, or internal scenario contracts.
    ''' </remarks>
    <EditorBrowsable(EditorBrowsableState.Advanced)>
    Public NotInheritable Class MASRenderVerification
        Private Sub New()
        End Sub

        ''' <summary>
        ''' Returns the default persistent local folder used for generated Render Verification product proof PNG assets.
        ''' </summary>
        Friend Shared ReadOnly Property DefaultOutputDirectory As String
            Get
                Dim localAppData As String = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)
                If String.IsNullOrWhiteSpace(localAppData) Then localAppData = Path.GetTempPath()
                Return Path.Combine(localAppData, "MAS", "Nexamas UI", "RenderVerification", "ProductProof")
            End Get
        End Property

        ''' <summary>
        ''' Generates approved/current/diff PNG assets and returns a product-facing immutable snapshot for dashboards, CI tools, and demos.
        ''' </summary>
        Public Shared Function CreateSnapshot(Optional outputDirectory As String = Nothing,
                                             Optional progress As IProgress(Of MASRenderVerificationProgressInfo) = Nothing,
                                             Optional themeId As String = Nothing) As MASRenderVerificationSnapshot
            Dim effectiveThemeId As String = NormalizeThemeId(themeId)
            Dim targets As IReadOnlyList(Of MASRenderVerificationTarget) = CreateThemeScopedTargets(MASRenderVerificationTargetCatalog.CreateShowcaseTargets(), effectiveThemeId)
            Dim rootDirectory As String = ResolveOutputDirectory(outputDirectory, effectiveThemeId)

            Return CreateSnapshotFromTargets(
                targets,
                AddressOf MASRenderVerificationShowcaseCapture.Capture,
                rootDirectory,
                progress)
        End Function

        ''' <summary>
        ''' Attaches the official Nexamas UI-owned Render Verification dashboard to a MASApplicationWindow.
        ''' Consumers receive a ready page and never build capture, catalog, diff, or PNG-preview UI themselves.
        ''' </summary>
        Public Shared Function AttachDashboard(window As MASApplicationWindow,
                                               Optional outputDirectory As String = Nothing,
                                               Optional themeId As String = Nothing) As IDisposable
            If window Is Nothing Then Throw New ArgumentNullException(NameOf(window))

            Dim dashboard As New MASRenderVerificationDashboardPage(outputDirectory, themeId)
            dashboard.Build(window)
            Return dashboard
        End Function

        ''' <summary>
        ''' Attaches the official Nexamas UI-owned Render Verification dashboard content to an existing
        ''' Application page-builder scope such as MASApplicationWindow.Pages. The host supplies only the
        ''' current MASApplicationWindow and MASApplicationLayoutPageBuilder; Nexamas UI still owns snapshot
        ''' generation, PNG persistence, preview surfaces, target identity, diff facts, and dashboard layout.
        ''' </summary>
        Public Shared Function AttachDashboard(window As MASApplicationWindow,
                                               page As MASApplicationLayoutPageBuilder,
                                               Optional outputDirectory As String = Nothing,
                                               Optional themeId As String = Nothing) As IDisposable
            If window Is Nothing Then Throw New ArgumentNullException(NameOf(window))
            If page Is Nothing Then Throw New ArgumentNullException(NameOf(page))

            Dim dashboard As New MASRenderVerificationDashboardPage(outputDirectory, themeId)
            dashboard.Build(window, page)
            Return dashboard
        End Function

        ''' <summary>
        ''' Internal execution seam used by Nexamas UI gates and the runtime harness to validate the
        ''' same snapshot gateway contract with a controlled target subset. It is deliberately Friend-only:
        ''' product consumers still receive only CreateSnapshot, AttachDashboard, and immutable snapshot entries.
        ''' </summary>
        Friend Shared Function CreateSnapshotFromTargets(targets As IReadOnlyList(Of MASRenderVerificationTarget),
                                                         capture As MASRenderCaptureHandler,
                                                         outputDirectory As String,
                                                         Optional progress As IProgress(Of MASRenderVerificationProgressInfo) = Nothing) As MASRenderVerificationSnapshot
            If capture Is Nothing Then Throw New ArgumentNullException(NameOf(capture))

            Dim root As String = If(String.IsNullOrWhiteSpace(outputDirectory), DefaultOutputDirectory, outputDirectory)
            Dim store As New MASRenderBaselineStore(root)
            Dim totalScenarios As Integer = CountScenarios(targets)
            ReportProgress(progress, 0, totalScenarios, "Preparing Render Verification target catalog...", String.Empty, String.Empty, 0.0R)

            Dim baselineMode As MASRenderBaselineMode = If(AllBaselinesExist(targets, store), MASRenderBaselineMode.CompareOnly, MASRenderBaselineMode.CreateMissing)
            Dim options As New MASRenderVerificationOptions(baselineMode, MASRenderDiffSettings.Strict)
            Dim runnerProgress As IProgress(Of MASRenderVerificationProgressInfo) = CreateGatewayProgress(progress)

            Dim result As MASRenderVerificationRunResult = MASRenderVerificationRunner.Run(targets, store, capture, options, runnerProgress)
            FinalizeDifferenceArtifacts(store, result.Diffs, progress, totalScenarios)
            Dim entries As IReadOnlyList(Of MASRenderVerificationEntry) = CreateEntries(targets, store, result)

            ReportProgress(progress, totalScenarios, totalScenarios, "Render Verification snapshot is ready.", String.Empty, String.Empty, 100.0R)
            Return New MASRenderVerificationSnapshot(store.RootDirectory, store.BaselineDirectory, store.CurrentDirectory, store.DifferenceDirectory, entries)
        End Function

        Private Shared Function ResolveOutputDirectory(outputDirectory As String,
                                                              themeId As String) As String
            Dim rootDirectory As String = If(String.IsNullOrWhiteSpace(outputDirectory), DefaultOutputDirectory, outputDirectory)
            Dim normalizedThemeId As String = NormalizeThemeId(themeId)
            If String.IsNullOrWhiteSpace(normalizedThemeId) Then Return rootDirectory

            Return Path.Combine(rootDirectory, "themes", SanitizePathSegment(normalizedThemeId))
        End Function

        Private Shared Function CreateThemeScopedTargets(targets As IReadOnlyList(Of MASRenderVerificationTarget),
                                                         themeId As String) As IReadOnlyList(Of MASRenderVerificationTarget)
            Dim normalizedThemeId As String = NormalizeThemeId(themeId)
            If String.IsNullOrWhiteSpace(normalizedThemeId) OrElse targets Is Nothing OrElse targets.Count = 0 Then
                Return targets
            End If

            Dim scopedTargets As New List(Of MASRenderVerificationTarget)(targets.Count)
            For Each target As MASRenderVerificationTarget In targets
                If target Is Nothing Then Continue For
                scopedTargets.Add(CreateThemeScopedTarget(target, normalizedThemeId))
            Next

            Return scopedTargets
        End Function

        Private Shared Function CreateThemeScopedTarget(target As MASRenderVerificationTarget,
                                                        themeId As String) As MASRenderVerificationTarget
            Dim scenarios As New List(Of MASRenderVerificationScenario)()
            For Each scenario As MASRenderVerificationScenario In target.Scenarios
                If scenario Is Nothing Then Continue For
                scenarios.Add(CreateThemeScopedScenario(scenario, themeId))
            Next

            Return New MASRenderVerificationTarget(target.Id, target.DisplayName, scenarios)
        End Function

        Private Shared Function CreateThemeScopedScenario(scenario As MASRenderVerificationScenario,
                                                          themeId As String) As MASRenderVerificationScenario
            Return New MASRenderVerificationScenario(
                id:=scenario.Id,
                componentKey:=scenario.ComponentKey,
                displayName:=scenario.DisplayName,
                stateKey:=scenario.StateKey,
                themeKey:=themeId,
                dpiScale:=scenario.DpiScale,
                widthPx:=scenario.WidthPx,
                heightPx:=scenario.HeightPx,
                tags:=scenario.Tags)
        End Function

        Private Shared Function NormalizeThemeId(themeId As String) As String
            Return If(themeId, String.Empty).Trim()
        End Function

        Private Shared Function SanitizePathSegment(value As String) As String
            Dim clean As String = If(value, String.Empty).Trim()
            If clean.Length = 0 Then Return "default"

            For Each invalidCharacter As Char In Path.GetInvalidFileNameChars()
                clean = clean.Replace(invalidCharacter, "_"c)
            Next

            If clean.Length = 0 Then clean = "default"
            Return clean
        End Function

        Private Shared Function CountScenarios(targets As IEnumerable(Of MASRenderVerificationTarget)) As Integer
            If targets Is Nothing Then Return 0

            Dim count As Integer = 0
            For Each target As MASRenderVerificationTarget In targets
                If target Is Nothing Then Continue For

                For Each scenario As MASRenderVerificationScenario In target.Scenarios
                    If scenario IsNot Nothing Then count += 1
                Next
            Next

            Return count
        End Function

        Friend Shared Sub ReportProgress(progress As IProgress(Of MASRenderVerificationProgressInfo),
                                         completedScenarios As Integer,
                                         totalScenarios As Integer,
                                         statusText As String,
                                         currentTarget As String,
                                         currentScenario As String,
                                         Optional percentOverride As Double = Double.NaN)
            If progress Is Nothing Then Return
            progress.Report(New MASRenderVerificationProgressInfo(completedScenarios, totalScenarios, statusText, currentTarget, currentScenario, percentOverride))
        End Sub

        Private Shared Function CreateGatewayProgress(progress As IProgress(Of MASRenderVerificationProgressInfo)) As IProgress(Of MASRenderVerificationProgressInfo)
            If progress Is Nothing Then Return Nothing
            Return New GatewayProgressReporter(progress)
        End Function

        Private NotInheritable Class GatewayProgressReporter
            Implements IProgress(Of MASRenderVerificationProgressInfo)

            Private ReadOnly _inner As IProgress(Of MASRenderVerificationProgressInfo)

            Friend Sub New(inner As IProgress(Of MASRenderVerificationProgressInfo))
                _inner = inner
            End Sub

            Public Sub Report(info As MASRenderVerificationProgressInfo) Implements IProgress(Of MASRenderVerificationProgressInfo).Report
                If _inner Is Nothing OrElse info Is Nothing Then Return
                Dim mappedPercent As Double = 5.0R + (info.Percent * 0.82R)
                MASRenderVerification.ReportProgress(_inner, info.CompletedScenarios, info.TotalScenarios, info.StatusText, info.CurrentTarget, info.CurrentScenario, mappedPercent)
            End Sub
        End Class

        Private Shared Sub FinalizeDifferenceArtifacts(store As MASRenderBaselineStore,
                                                         diffs As IReadOnlyList(Of MASRenderDiffResult),
                                                         progress As IProgress(Of MASRenderVerificationProgressInfo),
                                                         totalScenarios As Integer)
            If diffs Is Nothing OrElse diffs.Count = 0 Then
                ReportProgress(progress, totalScenarios, totalScenarios, "No render-verification results were available for diff artifact finalization.", String.Empty, String.Empty, 96.0R)
                Return
            End If

            Dim requiredDiffs As IReadOnlyList(Of MASRenderDiffResult) = diffs.Where(Function(diff) MASRenderDifferenceImageWriter.RequiresDifferenceArtifact(diff)).ToArray()
            If requiredDiffs.Count = 0 Then
                For Each diff As MASRenderDiffResult In diffs
                    MASRenderDifferenceImageWriter.WriteDifference(store, diff)
                Next
                ReportProgress(progress, totalScenarios, totalScenarios, "No visual-difference PNG artifacts are required; stale diff artifacts were cleared.", String.Empty, String.Empty, 96.0R)
                Return
            End If

            Dim completedArtifacts As Integer = 0
            For Each diff As MASRenderDiffResult In diffs
                If Not MASRenderDifferenceImageWriter.RequiresDifferenceArtifact(diff) Then
                    MASRenderDifferenceImageWriter.WriteDifference(store, diff)
                    Continue For
                End If

                Dim scenarioName As String = If(diff Is Nothing OrElse diff.Scenario Is Nothing, String.Empty, diff.Scenario.DisplayName)
                Dim beforePercent As Double = 87.0R + ((CDbl(completedArtifacts) / CDbl(requiredDiffs.Count)) * 9.0R)
                ReportProgress(progress, totalScenarios, totalScenarios, "Writing visual-difference PNG artifact " & (completedArtifacts + 1).ToString(Global.System.Globalization.CultureInfo.InvariantCulture) & "/" & requiredDiffs.Count.ToString(Global.System.Globalization.CultureInfo.InvariantCulture) & "...", String.Empty, scenarioName, beforePercent)
                MASRenderDifferenceImageWriter.WriteDifference(store, diff)
                completedArtifacts += 1
                Dim afterPercent As Double = 87.0R + ((CDbl(completedArtifacts) / CDbl(requiredDiffs.Count)) * 9.0R)
                ReportProgress(progress, totalScenarios, totalScenarios, "Visual-difference PNG artifact written.", String.Empty, scenarioName, afterPercent)
            Next
        End Sub

        Private Shared Function CreateEntries(targets As IEnumerable(Of MASRenderVerificationTarget),
                                              store As MASRenderBaselineStore,
                                              result As MASRenderVerificationRunResult) As IReadOnlyList(Of MASRenderVerificationEntry)
            Dim entries As New List(Of MASRenderVerificationEntry)()
            If targets Is Nothing OrElse result Is Nothing Then Return entries

            Dim diffByScenarioId As New Dictionary(Of String, MASRenderDiffResult)(StringComparer.OrdinalIgnoreCase)
            For Each diff As MASRenderDiffResult In result.Diffs
                If diff Is Nothing OrElse diff.Scenario Is Nothing OrElse String.IsNullOrWhiteSpace(diff.Scenario.Id) Then Continue For
                If Not diffByScenarioId.ContainsKey(diff.Scenario.Id) Then diffByScenarioId.Add(diff.Scenario.Id, diff)
            Next

            For Each target As MASRenderVerificationTarget In targets
                If target 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
                    Dim diff As MASRenderDiffResult = Nothing
                    If Not diffByScenarioId.TryGetValue(scenario.Id, diff) Then Continue For
                    entries.Add(CreateEntry(target, scenario, store, diff))
                Next
            Next

            Return entries
        End Function

        Private Shared Function AllBaselinesExist(targets As IEnumerable(Of MASRenderVerificationTarget),
                                                         store As MASRenderBaselineStore) As Boolean
            If targets Is Nothing OrElse store Is Nothing Then Return False

            Dim scenarioCount As Integer = 0
            For Each target As MASRenderVerificationTarget In targets
                If target Is Nothing Then Continue For

                For Each scenario As MASRenderVerificationScenario In target.Scenarios
                    If scenario Is Nothing Then Continue For
                    scenarioCount += 1
                    If Not store.BaselineExists(scenario) Then Return False
                Next
            Next

            Return scenarioCount > 0
        End Function

        Private Shared Function CreateEntry(target As MASRenderVerificationTarget,
                                            scenario As MASRenderVerificationScenario,
                                            store As MASRenderBaselineStore,
                                            diff As MASRenderDiffResult) As MASRenderVerificationEntry
            Return New MASRenderVerificationEntry(
                target.Id,
                target.DisplayName,
                scenario.Id,
                scenario.DisplayName,
                scenario.ComponentKey,
                scenario.StateKey,
                scenario.ThemeKey,
                scenario.DpiScale,
                scenario.WidthPx,
                scenario.HeightPx,
                store.GetBaselinePath(scenario),
                store.GetCurrentPath(scenario),
                store.GetDifferencePath(scenario),
                diff.Outcome,
                diff.PixelCount,
                diff.DifferentPixelCount,
                diff.MaxChannelDelta,
                diff.DifferenceRatio,
                diff.Message)
        End Function

    End Class

End Namespace
