Exporters/Export-Html.ps1

#Requires -Version 5.1
<#
.SYNOPSIS
    HTML exporter — executive compliance dashboard.
.DESCRIPTION
    Generates a self-contained HTML report with pass/fail gauge,
    category breakdown, control details, and remediation guidance.
#>

function Export-Html {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][array]$Results,
        [string]$OutputPath = '',
        [string]$Title = 'BSI IT-Grundschutz++ Compliance Report'
    )

    $summary = Get-BsiSummary
    $grouped = $Results | Group-Object -Property Category

    # Build category rows
    $categoryRows = ''
    foreach ($group in $grouped) {
        $catPassed = @($group.Group | Where-Object { $_.Status -eq [BsiCheckStatus]::Pass }).Count
        $catTotal  = $group.Group.Count
        $catPct    = if ($catTotal -gt 0) { [math]::Round(($catPassed / $catTotal) * 100) } else { 0 }
        $catColor  = if ($catPct -ge 80) { '#22c55e' } elseif ($catPct -ge 50) { '#eab308' } else { '#ef4444' }

        $categoryRows += " <tr>
        <td>$($group.Name)</td>
        <td>$catPassed / $catTotal</td>
        <td><div class='bar-bg'><div class='bar-fill' style='width:${catPct}%;background:$catColor'></div></div></td>
        <td>$catPct%</td>
      </tr>`n"

    }

    # Build control detail rows
    $controlRows = ''
    foreach ($r in $Results) {
        $statusColor = switch ($r.Status) {
            ([BsiCheckStatus]::Pass)  { '#22c55e' }
            ([BsiCheckStatus]::Fail)  { '#ef4444' }
            ([BsiCheckStatus]::Skip)  { '#eab308' }
            ([BsiCheckStatus]::Error) { '#a855f7' }
        }
        $sevColor = switch ($r.Severity) {
            ([BsiSeverity]::Critical) { '#ef4444' }
            ([BsiSeverity]::High)     { '#f97316' }
            ([BsiSeverity]::Medium)   { '#eab308' }
            ([BsiSeverity]::Low)      { '#22c55e' }
            ([BsiSeverity]::Info)     { '#6b7280' }
        }
        $details  = [System.Net.WebUtility]::HtmlEncode($r.Details)
        $remed    = if ($r.Remediation) { "<br><small class='remed'>-> $([System.Net.WebUtility]::HtmlEncode($r.Remediation))</small>" } else { '' }

        $controlRows += " <tr>
        <td><span class='badge' style='background:$sevColor'>$($r.Severity)</span></td>
        <td><strong>$($r.ControlId)</strong></td>
        <td>$($r.ControlTitle)</td>
        <td><span class='badge' style='background:$statusColor'>$(if ($r.Status -eq [BsiCheckStatus]::Pass) { 'PASS' } else { 'FAIL' })</span></td>
        <td>$details$remed</td>
      </tr>`n"

    }

    # Score color
    $scoreColor = if ($summary.Score -ge 90) { '#22c55e' } elseif ($summary.Score -ge 70) { '#eab308' } else { '#ef4444' }
    $scoreDeg = [math]::Round($summary.Score * 3.6)

    $html = @"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>$Title</title>
<style>
  * { box-sizing: border-box; margin: 0; padding: 0; }
  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f172a; color: #e2e8f0; padding: 2rem; }
  .container { max-width: 1200px; margin: 0 auto; }
  h1 { font-size: 1.5rem; margin-bottom: 0.5rem; color: #f1f5f9; }
  .subtitle { color: #94a3b8; font-size: 0.875rem; margin-bottom: 2rem; }
  .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
  .card { background: #1e293b; border-radius: 12px; padding: 1.5rem; text-align: center; }
  .card h2 { font-size: 2rem; margin-bottom: 0.25rem; }
  .card p { color: #94a3b8; font-size: 0.875rem; }
  .gauge-container { position: relative; width: 160px; height: 160px; margin: 0 auto 1rem; }
  .gauge-bg { fill: none; stroke: #334155; stroke-width: 12; }
  .gauge-fill { fill: none; stroke: $scoreColor; stroke-width: 12; stroke-linecap: round; transform: rotate(-90deg); transform-origin: 50% 50%; transition: stroke-dashoffset 1s; }
  .gauge-text { font-size: 2.5rem; font-weight: bold; fill: #f1f5f9; text-anchor: middle; dominant-baseline: central; }
  table { width: 100%; border-collapse: collapse; margin-bottom: 2rem; }
  th, td { padding: 0.75rem 1rem; text-align: left; border-bottom: 1px solid #1e293b; }
  th { background: #1e293b; color: #94a3b8; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; }
  tr:hover { background: #1e293b; }
  .badge { padding: 0.25rem 0.75rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 600; color: #fff; }
  .bar-bg { background: #334155; border-radius: 4px; height: 8px; width: 100%; }
  .bar-fill { height: 100%; border-radius: 4px; transition: width 0.5s; }
  .section { margin-bottom: 2rem; }
  .section h2 { font-size: 1.125rem; margin-bottom: 1rem; color: #f1f5f9; }
  .remed { color: #fbbf24; }
  footer { color: #475569; font-size: 0.75rem; text-align: center; margin-top: 2rem; }
</style>
</head>
<body>
<div class="container">
  <h1>$Title</h1>
  <p class="subtitle">Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss UTC') | BSI Grundschutz++ OSCAL</p>

  <div class="cards">
    <div class="card">
      <div class="gauge-container">
        <svg viewBox="0 0 160 160">
          <circle class="gauge-bg" cx="80" cy="80" r="68" />
          <circle class="gauge-fill" cx="80" cy="80" r="68"
            stroke-dasharray="427" stroke-dashoffset="$([math]::Round(427 - (427 * $summary.Score / 100)))" />
          <text class="gauge-text" x="80" y="80">$($summary.Score)%</text>
        </svg>
      </div>
      <p>Compliance Score</p>
    </div>
    <div class="card"><h2 style="color:#22c55e">$($summary.Passed)</h2><p>Passed</p></div>
    <div class="card"><h2 style="color:#ef4444">$($summary.Failed)</h2><p>Failed</p></div>
    <div class="card"><h2>$($summary.Total)</h2><p>Total Checks</p></div>
  </div>

  <div class="section">
    <h2>Category Breakdown</h2>
    <table>
      <thead><tr><th>Category</th><th>Passed / Total</th><th>Progress</th><th>Score</th></tr></thead>
      <tbody>
$categoryRows </tbody>
    </table>
  </div>

  <div class="section">
    <h2>Control Details</h2>
    <table>
      <thead><tr><th>Severity</th><th>Control</th><th>Title</th><th>Status</th><th>Details</th></tr></thead>
      <tbody>
$controlRows </tbody>
    </table>
  </div>

  <footer>
    BSI.AzCompliance v2.0.0 | BSI IT-Grundschutz++ | $(Get-Date -Format 'yyyy')
  </footer>
</div>
</body>
</html>
"@


    if ($OutputPath) {
        $dir = Split-Path -Parent $OutputPath
        if ($dir -and -not (Test-Path -LiteralPath $dir)) {
            New-Item -ItemType Directory -Path $dir -Force | Out-Null
        }
        $html | Set-Content -Path $OutputPath -Encoding UTF8
        Write-Verbose "[Export-Html] Written to $OutputPath"
    }

    return $html
}