Common/Export-ComplianceOverview.ps1

function Export-ComplianceOverview {
    <#
    .SYNOPSIS
        Renders the Compliance Overview HTML section from assessment findings.
    .DESCRIPTION
        Generates the compliance overview block including framework selector,
        coverage cards, status distribution bar, section filter, compliance
        matrix table, and embedded JSON for client-side filtering. Designed
        to be dot-sourced from Export-AssessmentReport.ps1 where helper
        functions (ConvertTo-HtmlSafe, Get-SvgHorizontalBar) are already
        defined.
    .PARAMETER Findings
        Array of finding objects with CheckId, Setting, Status, RiskSeverity,
        Section, and Frameworks hashtable.
    .PARAMETER ControlRegistry
        The control registry hashtable keyed by CheckId.
    .PARAMETER Frameworks
        Ordered array of framework definition hashtables from
        Import-FrameworkDefinitions.
    .PARAMETER FrameworkFilter
        Optional list of framework family names (CIS, NIST, etc.) to limit
        which frameworks appear in the overview.
    .PARAMETER Sections
        Array of section names from the assessment summary.
    .OUTPUTS
        System.String - HTML block for the compliance overview section.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [PSCustomObject[]]$Findings,

        [Parameter(Mandatory)]
        [hashtable]$ControlRegistry,

        [Parameter(Mandatory)]
        [hashtable[]]$Frameworks,

        [Parameter()]
        [string[]]$FrameworkFilter,

        [Parameter()]
        [string[]]$Sections
    )

    # Apply FrameworkFilter to narrow displayed frameworks
    $displayFrameworks = $Frameworks
    if ($FrameworkFilter -and $FrameworkFilter.Count -gt 0) {
        $displayFrameworks = @($Frameworks | Where-Object { $_.filterFamily -in $FrameworkFilter })
    }

    if ($displayFrameworks.Count -eq 0) {
        return ''
    }

    $html = [System.Text.StringBuilder]::new()

    $null = $html.AppendLine("<details class='section' open>")
    $null = $html.AppendLine("<summary><h2>Compliance Overview</h2></summary>")
    $null = $html.AppendLine("<p>Security findings mapped across compliance frameworks. Use the selector below to choose which frameworks to display.</p>")

    # Informational disclaimer
    $null = $html.AppendLine("<div class='cis-disclaimer'>")
    $null = $html.AppendLine("<strong>Informational Notice</strong>")
    $null = $html.AppendLine("<p>This compliance assessment is provided for <strong>informational purposes only</strong> and does not constitute a comprehensive security assessment, audit, or certification. Results reflect automated checks at a point in time and should not be considered conclusive. For a thorough security evaluation, consider engaging a qualified security professional.</p>")
    $null = $html.AppendLine("</div>")

    # Framework multi-selector (one checkbox per framework)
    $null = $html.AppendLine("<div class='fw-selector' id='fwSelector'>")
    $null = $html.AppendLine("<span class='fw-selector-label'>Frameworks:</span>")
    foreach ($fw in $displayFrameworks) {
        $null = $html.AppendLine("<label class='fw-checkbox'><input type='checkbox' value='$($fw.frameworkId)' checked> $($fw.label)</label>")
    }
    $null = $html.AppendLine("<span class='fw-selector-actions'><button type='button' id='fwSelectAll' class='fw-action-btn'>All</button><button type='button' id='fwSelectNone' class='fw-action-btn'>None</button></span>")
    $null = $html.AppendLine("</div>")

    # Status counts
    $totalFindings = $Findings.Count
    $passCount = @($Findings | Where-Object { $_.Status -eq 'Pass' }).Count
    $failCount = @($Findings | Where-Object { $_.Status -eq 'Fail' }).Count
    $warnCount = @($Findings | Where-Object { $_.Status -eq 'Warning' }).Count
    $reviewCount = @($Findings | Where-Object { $_.Status -eq 'Review' }).Count
    $infoCount = @($Findings | Where-Object { $_.Status -eq 'Info' }).Count
    $knownStatuses = @('Pass', 'Fail', 'Warning', 'Review', 'Info')
    $unknownCount = @($Findings | Where-Object { $_.Status -notin $knownStatuses }).Count

    # Status distribution bar chart
    if ($totalFindings -gt 0) {
        $segments = @(
            @{ Css = 'pass'; Pct = [math]::Round(($passCount / $totalFindings) * 100, 1); Count = $passCount; Label = 'Pass' }
            @{ Css = 'fail'; Pct = [math]::Round(($failCount / $totalFindings) * 100, 1); Count = $failCount; Label = 'Fail' }
            @{ Css = 'warning'; Pct = [math]::Round(($warnCount / $totalFindings) * 100, 1); Count = $warnCount; Label = 'Warning' }
            @{ Css = 'review'; Pct = [math]::Round(($reviewCount / $totalFindings) * 100, 1); Count = $reviewCount; Label = 'Review' }
        )
        if ($infoCount -gt 0) {
            $segments += @{ Css = 'info'; Pct = [math]::Round(($infoCount / $totalFindings) * 100, 1); Count = $infoCount; Label = 'Info' }
        }
        if ($unknownCount -gt 0) {
            $segments += @{ Css = 'unknown'; Pct = [math]::Round(($unknownCount / $totalFindings) * 100, 1); Count = $unknownCount; Label = 'Unknown' }
        }
        $barChart = Get-SvgHorizontalBar -Segments $segments
        $null = $html.AppendLine("<div class='compliance-status-bar'>")
        $null = $html.AppendLine("<div class='compliance-bar-header'><span class='compliance-bar-title'>Finding Status Distribution</span><span class='compliance-bar-total'>$totalFindings controls assessed</span></div>")
        $null = $html.AppendLine($barChart)
        $null = $html.AppendLine("<div class='hbar-legend'>")
        foreach ($seg in $segments) {
            if ($seg.Count -gt 0) {
                $dotClass = switch ($seg.Css) { 'pass' { 'success' } 'fail' { 'danger' } 'warning' { 'warning' } 'review' { 'info' } 'info' { 'neutral' } default { 'muted' } }
                $null = $html.AppendLine("<span class='hbar-legend-item'><span class='chart-legend-dot dot-$dotClass'></span>$($seg.Label) ($($seg.Count))</span>")
            }
        }
        $null = $html.AppendLine("</div>")
        $null = $html.AppendLine("</div>")
    }

    # Framework coverage cards (one per framework)
    $null = $html.AppendLine("<div class='exec-summary' id='fwCards'>")
    foreach ($fw in $displayFrameworks) {
        $fwId = $fw.frameworkId
        $isProfileBased = $fw.scoringMethod -eq 'profile-compliance'

        if ($isProfileBased -and $fw.profiles) {
            # Profile-based card (CIS, NIST) -- pass rate as primary, coverage bar as secondary
            $profileFindings = @($Findings | Where-Object { $_.Frameworks -and $_.Frameworks.ContainsKey($fwId) })
            $profilePass = @($profileFindings | Where-Object { $_.Status -eq 'Pass' }).Count
            $profileScored = $profileFindings.Count
            $profileScore = if ($profileScored -gt 0) { [math]::Round(($profilePass / $profileScored) * 100, 1) } else { 0 }
            $scoreDisplay = if ($profileScored -gt 0) { "$profileScore%" } else { 'N/A' }
            $scoreClass = if ($profileScored -eq 0) { '' } elseif ($profileScore -ge 80) { 'success' } elseif ($profileScore -ge 60) { 'warning' } else { 'danger' }
            $catalogTotal = $fw.totalControls
            $coverageLabel = if ($catalogTotal -gt 0) { "$profileScored of $catalogTotal assessed" } else { "$profileScored assessed" }
            $coveragePct = if ($catalogTotal -gt 0) { [math]::Min(100, [math]::Round(($profileScored / $catalogTotal) * 100, 0)) } else { 0 }
            $tooltip = if ($fw.description) { " title='$(ConvertTo-HtmlSafe -Text $fw.description)'" } else { '' }

            # Detect level-based profiles (L1/L2 suffix) for sub-metric breakdown
            $profileKeys = @($fw.profiles.PSObject.Properties.Name)
            $levelKeys = @($profileKeys | ForEach-Object { if ($_ -match '-L(\d+)$') { "L$($Matches[1])" } } | Sort-Object -Unique)
            $levelBreakdownHtml = ''
            if ($levelKeys.Count -ge 2) {
                foreach ($level in $levelKeys) {
                    $levelProfiles = @($profileKeys | Where-Object { $_ -like "*-$level" })
                    $levelFindings = @($profileFindings | Where-Object {
                        $fwData = $_.Frameworks[$fwId]
                        $fwProfiles = if ($fwData.profiles) { @($fwData.profiles) } else { @() }
                        $matched = $false
                        foreach ($lp in $levelProfiles) { if ($lp -in $fwProfiles) { $matched = $true; break } }
                        $matched
                    })
                    $lvlTotal = $levelFindings.Count
                    $lvlPass = @($levelFindings | Where-Object { $_.Status -eq 'Pass' }).Count
                    $lvlRate = if ($lvlTotal -gt 0) { [math]::Round(($lvlPass / $lvlTotal) * 100, 1) } else { 0 }
                    $lvlClass = if ($lvlTotal -eq 0) { 'neutral' } elseif ($lvlRate -ge 80) { 'success' } elseif ($lvlRate -ge 60) { 'warning' } else { 'danger' }
                    $lvlDisplay = if ($lvlTotal -gt 0) { "$lvlRate%" } else { 'N/A' }
                    $levelBreakdownHtml += "<div class='profile-level-row'><span class='profile-level-label'>$level</span><span class='badge badge-$lvlClass' style='font-size:7.5pt;padding:2px 6px;'>$lvlDisplay</span><span class='profile-level-detail'>$lvlPass/$lvlTotal pass</span></div>"
                }
            }

            $null = $html.AppendLine("<div class='stat-card fw-card $scoreClass' data-fw='$fwId' data-catalog-total='$catalogTotal'$tooltip><div class='stat-value'>$scoreDisplay</div><div class='stat-label'>$($fw.label)</div><div class='stat-sublabel'>$coverageLabel</div><div class='coverage-bar'><div class='coverage-fill' style='width: $coveragePct%'></div></div><div class='coverage-label'>$coveragePct% coverage</div>$levelBreakdownHtml</div>")
        }
        else {
            # Non-profile card -- pass rate as primary, coverage bar as secondary
            $mappedFindings = @($Findings | Where-Object { $_.Frameworks -and $_.Frameworks.ContainsKey($fwId) })
            $mappedControls = @($mappedFindings | ForEach-Object {
                $fwData = $_.Frameworks[$fwId]
                if ($fwData.controlId) { $fwData.controlId -split ';' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' } }
            } | Sort-Object -Unique)
            $mappedCount = $mappedControls.Count
            $mappedPass = @($mappedFindings | Where-Object { $_.Status -eq 'Pass' }).Count
            $mappedTotal = $mappedFindings.Count
            $passRate = if ($mappedTotal -gt 0) { [math]::Round(($mappedPass / $mappedTotal) * 100, 1) } else { 0 }
            $passDisplay = if ($mappedTotal -gt 0) { "$passRate%" } else { 'N/A' }
            $passClass = if ($mappedTotal -eq 0) { '' } elseif ($passRate -ge 80) { 'success' } elseif ($passRate -ge 60) { 'warning' } else { 'danger' }
            $totalCount = $fw.totalControls
            $coveragePct = if ($totalCount -gt 0) { [math]::Min(100, [math]::Round(($mappedCount / $totalCount) * 100, 0)) } else { 0 }
            $coverageLabel = if ($totalCount -gt 0) { "$mappedTotal of $totalCount assessed" } else { "$mappedTotal assessed" }
            $coverageBarHtml = if ($totalCount -gt 0) { "<div class='coverage-bar'><div class='coverage-fill' style='width: $coveragePct%'></div></div><div class='coverage-label'>$coveragePct% coverage</div>" } else { '' }
            $tooltip = if ($fw.description) { " title='$(ConvertTo-HtmlSafe -Text $fw.description)'" } else { '' }
            $null = $html.AppendLine("<div class='stat-card fw-card $passClass' data-fw='$fwId' data-catalog-total='$totalCount'$tooltip><div class='stat-value'>$passDisplay</div><div class='stat-label'>$($fw.label)</div><div class='stat-sublabel'>$coverageLabel</div>$coverageBarHtml</div>")
        }
    }
    $null = $html.AppendLine("</div>")

    # Status filter (multi-select checkboxes)
    $null = $html.AppendLine("<div class='status-filter' id='statusFilter'>")
    $null = $html.AppendLine("<span class='status-filter-label'>Status:</span>")
    $null = $html.AppendLine("<label class='status-checkbox status-fail'><input type='checkbox' value='fail' checked> Fail ($failCount)</label>")
    if ($warnCount -gt 0) {
        $null = $html.AppendLine("<label class='status-checkbox status-warning'><input type='checkbox' value='warning' checked> Warning ($warnCount)</label>")
    }
    if ($reviewCount -gt 0) {
        $null = $html.AppendLine("<label class='status-checkbox status-review'><input type='checkbox' value='review' checked> Review ($reviewCount)</label>")
    }
    $null = $html.AppendLine("<label class='status-checkbox status-pass'><input type='checkbox' value='pass' checked> Pass ($passCount)</label>")
    if ($infoCount -gt 0) {
        $null = $html.AppendLine("<label class='status-checkbox status-info'><input type='checkbox' value='info' checked> Info ($infoCount)</label>")
    }
    if ($unknownCount -gt 0) {
        $null = $html.AppendLine("<label class='status-checkbox status-unknown'><input type='checkbox' value='unknown' checked> Unknown ($unknownCount)</label>")
    }
    if ($infoCount -gt 0) {
        $null = $html.AppendLine("<span class='info-note-inline'><span class='badge badge-neutral'>Info</span> = no pass/fail criteria; not included in pass rates</span>")
    }
    $null = $html.AppendLine("<span class='fw-selector-actions'><button type='button' id='statusSelectAll' class='fw-action-btn'>All</button><button type='button' id='statusSelectNone' class='fw-action-btn'>None</button></span>")
    $null = $html.AppendLine("</div>")

    # Section filter (scopes compliance view by assessment domain)
    $uniqueSections = @($Findings | Select-Object -ExpandProperty Section -ErrorAction SilentlyContinue | Where-Object { $_ } | Sort-Object -Unique)
    if ($uniqueSections.Count -gt 1) {
        $null = $html.AppendLine("<div class='section-filter' id='sectionFilter'>")
        $null = $html.AppendLine("<span class='section-filter-label'>Sections:</span>")
        foreach ($sec in $uniqueSections) {
            $secCount = @($Findings | Where-Object { $_.Section -eq $sec }).Count
            $null = $html.AppendLine("<label class='section-checkbox'><input type='checkbox' value='$(ConvertTo-HtmlSafe -Text $sec)' checked> $(ConvertTo-HtmlSafe -Text $sec) ($secCount)</label>")
        }
        $null = $html.AppendLine("<span class='fw-selector-actions'><button type='button' id='sectionSelectAll' class='fw-action-btn'>All</button><button type='button' id='sectionSelectNone' class='fw-action-btn'>None</button></span>")
        $null = $html.AppendLine("</div>")
    }

    # Unified compliance matrix table (one column per framework)
    $null = $html.AppendLine("<div class='table-wrapper'>")
    $null = $html.AppendLine("<table class='data-table matrix-table' id='complianceTable'>")

    # Header row -- fixed columns + one column per framework
    $headerCols = "<th scope='col'>Control</th><th scope='col'>Description</th><th scope='col'>Status</th><th scope='col'>Severity</th>"
    foreach ($fw in $displayFrameworks) {
        $headerCols += "<th scope='col' class='fw-col' data-fw='$($fw.frameworkId)'>$($fw.label)</th>"
    }
    $null = $html.AppendLine("<thead><tr>$headerCols</tr></thead>")
    $null = $html.AppendLine("<tbody>")

    # Sort findings by CheckId (groups by collector area)
    $matrixFindings = @($Findings | Sort-Object -Property CheckId)
    foreach ($finding in $matrixFindings) {
        $statusClass = switch ($finding.Status) {
            'Pass'    { 'badge-success' }
            'Fail'    { 'badge-failed' }
            'Warning' { 'badge-warning' }
            'Review'  { 'badge-info' }
            'Info'    { 'badge-neutral' }
            default   { 'badge-skipped' }
        }
        $statusBadge = "<span class='badge $statusClass'>$($finding.Status)</span>"
        $checkRef = ConvertTo-HtmlSafe -Text $finding.CheckId
        $settingText = ConvertTo-HtmlSafe -Text $finding.Setting

        $null = $html.AppendLine("<tr class='cis-row-$($finding.Status.ToLower())' data-section='$(ConvertTo-HtmlSafe -Text $finding.Section)'>")
        $severityClass = switch ($finding.RiskSeverity) {
            'Critical' { 'badge-critical' }
            'High'     { 'badge-failed' }
            'Medium'   { 'badge-warning' }
            'Low'      { 'badge-info' }
            'Info'     { 'badge-neutral' }
            default    { 'badge-neutral' }
        }
        $severityBadge = "<span class='badge $severityClass'>$($finding.RiskSeverity)</span>"

        $null = $html.AppendLine("<td class='cis-id'>$checkRef</td>")
        $null = $html.AppendLine("<td>$settingText</td>")
        $null = $html.AppendLine("<td>$statusBadge</td>")
        $null = $html.AppendLine("<td>$severityBadge</td>")

        # One cell per framework -- profile tags inline for profile-based frameworks
        foreach ($fw in $displayFrameworks) {
            $fwId = $fw.frameworkId
            $fwData = if ($finding.Frameworks -and $finding.Frameworks.ContainsKey($fwId)) { $finding.Frameworks[$fwId] } else { $null }
            if ($fwData -and $fwData.controlId) {
                $controlId = $fwData.controlId
                $tagHtml = "<span class='fw-tag $($fw.css)'>$(ConvertTo-HtmlSafe -Text $controlId)</span>"
                # Add inline profile tags for profile-based frameworks
                if ($fwData.profiles -and $fwData.profiles.Count -gt 0) {
                    $profileTags = ($fwData.profiles | ForEach-Object { "<span class='fw-profile-tag'>$_</span>" }) -join ''
                    $tagHtml += " $profileTags"
                }
                $null = $html.AppendLine("<td class='fw-col framework-refs' data-fw='$fwId'>$tagHtml</td>")
            }
            else {
                $null = $html.AppendLine("<td class='fw-col framework-refs' data-fw='$fwId'><span class='fw-unmapped'>&mdash;</span></td>")
            }
        }
        $null = $html.AppendLine("</tr>")
    }

    $null = $html.AppendLine("</tbody></table>")
    $null = $html.AppendLine("</div>")
    $null = $html.AppendLine("<div id='complianceNoResults' class='no-results' style='display:none'><p>No findings match the current filter selection.</p></div>")

    # Embed compliance data for client-side filtering/recalculation
    $complianceJson = @($Findings | ForEach-Object {
        $fwMap = [ordered]@{}
        if ($_.Frameworks) {
            foreach ($fw in $displayFrameworks) {
                $fwId = $fw.frameworkId
                if ($_.Frameworks.ContainsKey($fwId)) {
                    $fwEntry = $_.Frameworks[$fwId]
                    $jsEntry = @{ id = $fwEntry.controlId }
                    if ($fwEntry.profiles -and $fwEntry.profiles.Count -gt 0) {
                        $jsEntry['p'] = @($fwEntry.profiles)
                    }
                    $fwMap[$fwId] = $jsEntry
                }
            }
        }
        [PSCustomObject]@{
            c  = $_.CheckId
            s  = $_.Section
            st = $_.Status
            sv = $_.RiskSeverity
            fw = $fwMap
        }
    }) | ConvertTo-Json -Compress -Depth 4
    $null = $html.AppendLine("<script>var complianceData = $complianceJson;</script>")
    $null = $html.AppendLine("</details>")

    return $html.ToString()
}