Private/Export/Export-HtmlReport.ps1

function Export-HtmlReport {
    <#
    .SYNOPSIS
    Generates an HTML report of the policy compliance analysis.
     
    .DESCRIPTION
    Creates a comprehensive HTML report with:
    - Global compliance score with visual gauge and gradient background
    - Score cards for ALZ and MCSB with progress bars
    - Metrics pills (Matched, Missing, Version Mismatch, Extra)
    - Summary tables for custom initiatives, MCSB initiatives, and individual policies
    - Baseline coverage details (ALZ and MCSB)
    - Interactive filtering and search with DataTables
    - Responsive design with modern UI
    - Lifecycle badges (Deprecated, Preview)
     
    This version matches the rich UI from the legacy script.
     
    .PARAMETER Summary
    Array of summary objects from comparisons.
     
    .PARAMETER Baseline
    Array of baseline policy objects.
     
    .PARAMETER Scores
    Scores object from Calculate-ComplianceScores with AlzScore, McsbScore, GlobalScore.
     
    .PARAMETER Metrics
    Additional metrics (assignment counts, details, etc.).
     
    .PARAMETER AssignedById
    Hashtable of assigned policies by ID for status calculation.
     
    .PARAMETER AssignedByNormName
    Hashtable of assigned policies by normalized name for status calculation.
     
    .PARAMETER AssignedEffects
    Hashtable of policy effects for display.
     
    .PARAMETER MatchByNameOnly
    Whether name-only matching was used.
     
    .PARAMETER OutputPath
    Full path to the output HTML file.
     
    .PARAMETER LogoPath
    Optional path to logo image file.
     
    .PARAMETER ProjectName
    Name of the project for branding.
     
    .PARAMETER ProjectVersion
    Version number for display.
     
    .PARAMETER Scope
    Scope of the analysis (MG ID or Subscription ID).
     
    .EXAMPLE
    Export-HtmlReport -Summary $summary `
                      -Baseline $baseline `
                      -Scores $scores `
                      -Metrics $metrics `
                      -AssignedById $assignedById `
                      -AssignedByNormName $assignedByNormName `
                      -AssignedEffects $assignedEffects `
                      -MatchByNameOnly $true `
                      -OutputPath "C:\Reports\Report.html" `
                      -ProjectName "AzurePolicyWatch" `
                      -ProjectVersion "1.1.0" `
                      -Scope "/providers/Microsoft.Management/managementGroups/MyMG"
     
    Generates HTML report at specified path.
     
    .OUTPUTS
    None. Writes HTML file to disk.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [object[]]$Summary,
        
        [Parameter(Mandatory)]
        [object[]]$Baseline,
        
        [Parameter()]
        [object]$Scores = $null,
        
        [Parameter()]
        [hashtable]$Metrics = @{},
        
        [Parameter()]
        [hashtable]$AssignedById = @{},
        
        [Parameter()]
        [hashtable]$AssignedByNormName = @{},
        
        [Parameter()]
        [hashtable]$AssignedEffects = @{},
        
        [Parameter()]
        [bool]$MatchByNameOnly = $false,
        
        [Parameter(Mandatory)]
        [string]$OutputPath,
        
        [Parameter()]
        [string]$LogoPath = "",
        
        [Parameter()]
        [string]$ProjectName = "AzurePolicyWatch",
        
        [Parameter()]
        [string]$ProjectVersion = "1.1.0",

        [Parameter()]
        [string]$Scope = "",
        
        [Parameter()]
        [bool]$ExcludeExtraPolicies = $false
    )
    
    Write-Host "📄 Generating HTML report..." -ForegroundColor Cyan
    

    Write-Verbose "AssignedById count: $($AssignedById.Count)"
    Write-Verbose "AssignedByNormName count: $($AssignedByNormName.Count)"
    Write-Verbose "AssignedEffects count: $($AssignedEffects.Count)"
    

    if ($null -eq $Scores) {
        $Scores = [PSCustomObject]@{
            AlzScore = 0
            McsbScore = 0
            GlobalScore = 0
        }
        Write-Warning "Scores not provided, using defaults (0%)"
    }
    

    $totalMatched = if ($Metrics.GlobalMetrics -and $Metrics.GlobalMetrics['TotalDeployed']) { 
        $Metrics.GlobalMetrics['TotalDeployed'] 
    } else { 0 }
    
    $alzMissingVal = if ($Metrics.AlzMetrics -and $Metrics.AlzMetrics['Missing']) { $Metrics.AlzMetrics['Missing'] } else { 0 }
    $mcsbMissingVal = if ($Metrics.McsbMetrics -and $Metrics.McsbMetrics['Missing']) { $Metrics.McsbMetrics['Missing'] } else { 0 }
    $totalMissing = $alzMissingVal + $mcsbMissingVal
    
    

$totalExtra = if ($ExcludeExtraPolicies) { 
    0 
} elseif ($Metrics.GlobalMetrics -and $Metrics.GlobalMetrics['TotalExtra']) { 
    $Metrics.GlobalMetrics['TotalExtra'] 
} else { 

    ($Summary | Where-Object { $_.AssignmentStatus -eq 'Extra' }).Count
}
$extraFilteredOut = $ExcludeExtraPolicies
    $totalVersionMismatch = 0  
    
    $alzBaselineCount = if ($Metrics.AlzMetrics -and $Metrics.AlzMetrics['BaselineCount']) { 
        $Metrics.AlzMetrics['BaselineCount'] 
    } else { 0 }
    $alzDeployed = if ($Metrics.AlzMetrics -and $Metrics.AlzMetrics['Deployed']) { 
        $Metrics.AlzMetrics['Deployed'] 
    } else { 0 }
    $alzMissing = $alzMissingVal
    
    $mcsbBaselineCount = if ($Metrics.McsbMetrics -and $Metrics.McsbMetrics['BaselineCount']) { 
        $Metrics.McsbMetrics['BaselineCount'] 
    } else { 0 }
    $mcsbDeployed = if ($Metrics.McsbMetrics -and $Metrics.McsbMetrics['Deployed']) { 
        $Metrics.McsbMetrics['Deployed'] 
    } else { 0 }
    $mcsbMissing = $mcsbMissingVal
    
    $totalBaseline = if ($Metrics.GlobalMetrics -and $Metrics.GlobalMetrics['TotalBaseline']) { 
        $Metrics.GlobalMetrics['TotalBaseline'] 
    } else { ($alzBaselineCount + $mcsbBaselineCount) }
    

    $summaryInitiatives = @($Summary | Where-Object { -not $_.IsIndividualPolicy })
    $summaryIndividualPolicies = @($Summary | Where-Object { $_.IsIndividualPolicy })
    
    $summaryCustom = @($summaryInitiatives | Where-Object { -not $_.IsMCSB })
    $summaryMcsb = @($summaryInitiatives | Where-Object { $_.IsMCSB })
    

$assignmentStatusCache = @{}
foreach ($policy in $Baseline) {
    $isAssigned = Test-PolicyAssigned -PolicyDisplayName $policy.PolicyDisplayName `
                                    -PolicyDefinitionId $policy.PolicyDefinitionId `
                                    -AssignedById $AssignedById `
                                    -AssignedByNormName $AssignedByNormName `
                                    -MatchByNameOnly $MatchByNameOnly
    
    $assignmentStatusCache[$policy.PolicyDefinitionId] = if ($isAssigned) { "Matched" } else { "Missing" }
}


$baselineMcsbView = $Baseline | Where-Object { $_.BaselineSources -like "*MCSB*" } |
    Select-Object PolicyDisplayName, PolicyDefinitionId, Version, PolicyType, BaselineSources,
        @{Name="AssignmentStatus"; Expression={
            $assignmentStatusCache[$_.PolicyDefinitionId]  
        }},
        @{Name="Effect"; Expression={
                $effect = $null
                if ($AssignedEffects.ContainsKey($_.PolicyDefinitionId)) {
                    $effect = $AssignedEffects[$_.PolicyDefinitionId]
                }
                if (-not $effect) {
                    $nk = Normalize-PolicyName $_.PolicyDisplayName
                    if ($nk -and $AssignedEffects.ContainsKey($nk)) {
                        $effect = $AssignedEffects[$nk]
                    }
                }

                if ($effect -and $effect -match "\[parameters\('([^']+)'\)\]") {
                    $effect = "Parameterized"
                }
                if ($effect) { $effect } else { "-" }
            }}
    
    $baselineAlzView = $Baseline | Where-Object { $_.BaselineSources -like "*ALZ*" } |
        Select-Object PolicyDisplayName, PolicyDefinitionId, Version, PolicyType, BaselineSources,
            @{Name="AssignmentStatus"; Expression={
                if (Test-PolicyAssigned -PolicyDisplayName $_.PolicyDisplayName `
                                      -PolicyDefinitionId $_.PolicyDefinitionId `
                                      -AssignedById $AssignedById `
                                      -AssignedByNormName $AssignedByNormName `
                                      -MatchByNameOnly $MatchByNameOnly) { "Matched" } else { "Missing" }
            }},
            @{Name="Effect"; Expression={
                $effect = $null
                if ($AssignedEffects.ContainsKey($_.PolicyDefinitionId)) {
                    $effect = $AssignedEffects[$_.PolicyDefinitionId]
                }
                if (-not $effect) {
                    $nk = Normalize-PolicyName $_.PolicyDisplayName
                    if ($nk -and $AssignedEffects.ContainsKey($nk)) {
                        $effect = $AssignedEffects[$nk]
                    }
                }

                if ($effect -and $effect -match "\[parameters\('([^']+)'\)\]") {
                    $effect = "Parameterized"
                }
                if ($effect) { $effect } else { "-" }
            }}
    

    $scoreColor = if ($Scores.GlobalScore -ge 90) { "#10b981" }      # Green
                  elseif ($Scores.GlobalScore -ge 75) { "#06b6d4" }  # Cyan
                  elseif ($Scores.GlobalScore -ge 50) { "#f59e0b" }  # Yellow/Orange
                  else { "#ef4444" }                                 # Red
    
    $complianceLevel = if ($Scores.GlobalScore -ge 90) { "Excellent" }
                       elseif ($Scores.GlobalScore -ge 75) { "Good" }
                       elseif ($Scores.GlobalScore -ge 50) { "Average" }
                       else { "Poor" }
    
    # Encode logo as base64 if provided
$logoBase64 = ""
$logoDataUri = ""  # ✅ Initialiser pour éviter erreur si pas de logo

if ($LogoPath -and (Test-Path $LogoPath)) {
    try {
        Write-Verbose "Encoding logo from: $LogoPath"
        $logoBytes = [System.IO.File]::ReadAllBytes($LogoPath)
        $logoBase64 = [Convert]::ToBase64String($logoBytes)
        $logoExt = [System.IO.Path]::GetExtension($LogoPath).TrimStart('.')
        $logoDataUri = "data:image/$logoExt;base64,$logoBase64"
        Write-Verbose "✅ Logo encoded successfully (size: $($logoBytes.Length) bytes)"
    }
    catch {
        Write-Warning "Failed to encode logo: $($_.Exception.Message)"
        $logoDataUri = ""
    }
} else {
    if ($LogoPath) {
        Write-Warning "Logo file not found: $LogoPath"
    } else {
        Write-Verbose "No logo provided, using default icon (🔍) in hero section"
    }
}
    
    # Build HTML tables
    $columnsToHide = @('IsMCSB', 'IsExtra', 'IsIndividualPolicy')
    
    $sumCustomHtml = if ($summaryCustom.Count -gt 0) {
        $filtered = $summaryCustom | Select-Object * -ExcludeProperty $columnsToHide
        Build-HtmlContent -Data $filtered -TableId "tblSummaryCustom"
    } else {
        "<p>No custom initiatives found.</p>"
    }
    $sumMcsbHtml = if ($summaryMcsb.Count -gt 0) {
        $filtered = $summaryMcsb | Select-Object * -ExcludeProperty $columnsToHide
        Build-HtmlContent -Data $filtered -TableId "tblSummaryMcsb"
    } else {
        "<p>No MCSB initiatives found.</p>"
    }
    $sumIndividualHtml = if ($summaryIndividualPolicies.Count -gt 0) {
        $filtered = $summaryIndividualPolicies | Select-Object * -ExcludeProperty $columnsToHide
        Build-HtmlContent -Data $filtered -TableId "tblIndividual"
    } else {
        "<p>No individual policies found.</p>"
    }
    $mcsbHtml = if ($baselineMcsbView.Count -gt 0) {
        Build-HtmlContent -Data $baselineMcsbView -TableId "tblMCSB"
    } else {
        "<p>No MCSB baseline policies.</p>"
    }
    $alzHtml = if ($baselineAlzView.Count -gt 0) {
        Build-HtmlContent -Data $baselineAlzView -TableId "tblALZ"
    } else {
        "<p>No ALZ baseline policies.</p>"
    }
    
    # Apply table CSS class for styling
    $sumCustomHtml = $sumCustomHtml -replace '<table>', '<table class="data-table">'
    $sumCustomHtml = $sumCustomHtml -replace '<td>(-\d+)</td>', '<td style="color: #d32f2f; font-weight: bold; background-color: #ffebee;">$1</td>'
    
    $sumMcsbHtml = $sumMcsbHtml -replace '<table>', '<table class="data-table">'
    $sumMcsbHtml = $sumMcsbHtml -replace '<td>(-\d+)</td>', '<td style="color: #d32f2f; font-weight: bold; background-color: #ffebee;">$1</td>'
    
    if ($sumIndividualHtml -ne "<p>No individual policies found.</p>") {
        $sumIndividualHtml = $sumIndividualHtml -replace '<table>', '<table class="data-table">'
    }
    
    $mcsbHtml = $mcsbHtml -replace '<table>', '<table class="data-table">'
    $alzHtml = $alzHtml -replace '<table>', '<table class="data-table">'
    
    # Build comprehensive HTML document with rich UI
$style = @"
<style>
/* ========== GLOBAL STYLES ========== */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
 
body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
    background: #f5f7fa; /* ✅ CHANGEMENT: Fond gris clair au lieu de violet */
    padding: 20px;
    min-height: 100vh;
}
 
.container {
    max-width: 1800px; /* ✅ CHANGEMENT: Élargi pour éviter scroll horizontal */
    margin: 0 auto;
}
 
/* ========== HEADER / BRAND ========== */
.brand {
    background: white;
    padding: 24px 32px;
    border-radius: 16px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 24px;
    border: 1px solid #e0e4e8;
}
 
.brand-logo {
    height: 50px;
    width: auto;
}
 
.brand-text {
    display: flex;
    flex-direction: column;
}
 
.brand-title {
    font-size: 32px;
    font-weight: 800;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
}
 
.brand-sub {
    color: #666;
    font-size: 14px;
    margin-top: 4px;
    font-weight: 500;
}
 
.version-badge {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    padding: 10px 24px;
    border-radius: 30px;
    font-size: 14px;
    font-weight: 700;
    box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
    letter-spacing: 0.5px;
}
 
/* ========== HERO SCORE SECTION ========== */
.hero-score {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    padding: 50px; /* ✅ CHANGEMENT: Réduit pour tenir sur une page */
    border-radius: 16px;
    text-align: center;
    margin-bottom: 24px;
    box-shadow: 0 8px 24px rgba(102, 126, 234, 0.3);
    position: relative;
    overflow: hidden;
}
 
.hero-score::before {
    content: '';
    position: absolute;
    top: -50%;
    right: -50%;
    width: 200%;
    height: 200%;
    background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
    animation: pulse 20s ease-in-out infinite;
}
 
/* ========== LOGO ANIMÉ EN ARRIÈRE-PLAN ========== */
 
@keyframes floatLogo {
    0%, 100% {
        transform: translate(-50%, -50%) scale(1);
        opacity: 0.15;
    }
    50% {
        transform: translate(-50%, -50%) scale(1.05); /* ✅ Moins de grossissement */
        opacity: 0.18; /* ✅ Moins de variation d'opacité */
    }
}
 
/* Animation pulse pour l'effet radial (déjà présent) */
@keyframes pulse {
    0%, 100% { transform: scale(1) rotate(0deg); opacity: 0.3; }
    50% { transform: scale(1.1) rotate(180deg); opacity: 0.5; }
}
 
/* Logo par défaut (icône) avec animation */
.hero-score::after {
    content: '🔍';
    position: absolute;
    font-size: 200px;
    top: 50%;
    left: 50%;
    z-index: 0;
    color: white;
    opacity: 0.08;
    transform: translate(-50%, -50%);
    animation: floatLogo 40s ease-in-out infinite;
    pointer-events: none;
}
 
/* Override pour logo image fourni */
.hero-score[data-has-logo="true"]::after {
    content: '';
    font-size: 0; /* Cache l'icône texte */
    background-image: var(--logo-url);
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center;
    width: 550px;
    height: 550px;
    opacity: 0.18;
    filter: blur(0px);
    /* L'animation floatLogo est héritée automatiquement */
}
 
.score-value {
    font-size: 72px; /* ✅ CHANGEMENT: Réduit pour compacité */
    font-weight: 900;
    margin-bottom: 16px;
    text-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
    position: relative;
    z-index: 1;
    letter-spacing: -2px;
}
 
.score-label {
    font-size: 18px; /* ✅ CHANGEMENT: Réduit */
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 3px;
    margin-bottom: 16px;
    opacity: 0.95;
    position: relative;
    z-index: 1;
}
 
.score-badge {
    background: rgba(255, 255, 255, 0.25);
    backdrop-filter: blur(10px);
    padding: 10px 24px;
    border-radius: 25px;
    display: inline-block;
    margin-top: 12px;
    font-weight: 700;
    font-size: 16px;
    position: relative;
    z-index: 1;
    border: 2px solid rgba(255, 255, 255, 0.3);
}
 
.progress-bar-container {
    background: rgba(255, 255, 255, 0.2);
    border-radius: 10px;
    height: 20px;
    overflow: hidden;
    margin-top: 20px;
    box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.2);
    position: relative;
    z-index: 1;
}
 
.progress-bar {
    background: linear-gradient(90deg, #ffffff 0%, #f0f0f0 100%);
    height: 100%;
    border-radius: 10px;
    transition: width 2s cubic-bezier(0.4, 0, 0.2, 1);
    box-shadow: 0 2px 8px rgba(255, 255, 255, 0.5);
}
 
.progress-text {
    text-align: center;
    margin-top: 10px;
    font-size: 14px;
    opacity: 0.95;
    position: relative;
    z-index: 1;
    font-weight: 600;
}
 
/* ========== METRICS PILLS ========== */
.metrics-pills {
    display: flex;
    justify-content: center;
    gap: 14px;
    flex-wrap: wrap;
    margin-top: 20px;
    position: relative;
    z-index: 1;
}
 
.pill {
    background: rgba(255, 255, 255, 0.2);
    backdrop-filter: blur(10px);
    padding: 10px 20px;
    border-radius: 20px;
    font-size: 14px;
    font-weight: 600;
    border: 1px solid rgba(255, 255, 255, 0.3);
    transition: all 0.3s ease;
}
 
.pill:hover {
    background: rgba(255, 255, 255, 0.3);
    transform: translateY(-2px);
}
 
.pill .num {
    font-size: 18px;
    font-weight: 900;
    margin-right: 6px;
}
 
.pill.matched .num { color: #4caf50; }
.pill.missing .num { color: #f44336; }
.pill.version .num { color: #ff9800; }
.pill.extra .num { color: #2196f3; }
 
/* ========== SCORE CARDS ========== */
.score-cards {
    display: grid;
    grid-template-columns: repeat(2, 1fr); /* ✅ CHANGEMENT: 2 colonnes fixes */
    gap: 20px;
    margin-bottom: 24px;
}
 
.score-card {
    background: white;
    border-radius: 12px;
    padding: 24px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
    border-top: 4px solid;
    transition: all 0.3s ease;
    border: 1px solid #e0e4e8;
}
 
.score-card:hover {
    transform: translateY(-4px);
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
}
 
.score-card.alz { border-top-color: #0078D4; }
.score-card.mcsb { border-top-color: #4caf50; }
 
.card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 14px;
    font-size: 12px;
    color: #666;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}
 
.card-score {
    font-size: 48px;
    font-weight: 900;
    margin-bottom: 10px;
    letter-spacing: -1px;
}
 
.score-card.alz .card-score { color: #0078D4; }
.score-card.mcsb .card-score { color: #4caf50; }
 
.card-subtitle {
    font-size: 12px;
    color: #999;
    font-weight: 600;
}
 
.card-progress {
    background: #f0f0f0;
    border-radius: 10px;
    height: 8px;
    overflow: hidden;
    margin: 14px 0;
}
 
.card-progress-bar {
    height: 100%;
    border-radius: 10px;
    transition: width 1.5s cubic-bezier(0.4, 0, 0.2, 1);
}
 
.card-progress-bar.alz {
    background: linear-gradient(90deg, #0078D4 0%, #4da6ff 100%);
}
 
.card-progress-bar.mcsb {
    background: linear-gradient(90deg, #4caf50 0%, #81c784 100%);
}
 
.card-stats {
    display: flex;
    justify-content: space-between;
    font-size: 12px;
    color: #666;
    font-weight: 600;
}
 
/* ========== FILTERS ========== */
#filters {
    background: white;
    padding: 20px;
    border-radius: 12px;
    margin-bottom: 20px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
    display: flex;
    gap: 14px;
    align-items: center;
    flex-wrap: wrap;
    border: 1px solid #e0e4e8;
}
 
#globalSearch {
    flex: 1;
    min-width: 280px;
    padding: 10px 16px;
    border: 2px solid #e0e4e8;
    border-radius: 10px;
    font-size: 14px;
    font-weight: 500;
    transition: all 0.3s ease;
}
 
#globalSearch:focus {
    outline: none;
    border-color: #667eea;
    box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
 
#statusFilter {
    padding: 10px 16px;
    border: 2px solid #e0e4e8;
    border-radius: 10px;
    font-size: 14px;
    font-weight: 500;
    background: white;
    cursor: pointer;
    transition: all 0.3s ease;
}
 
#statusFilter:hover {
    border-color: #667eea;
}
 
label {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 14px;
    color: #666;
    font-weight: 600;
    cursor: pointer;
}
 
label:hover {
    color: #667eea;
}
 
label input[type="checkbox"] {
    width: 16px;
    height: 16px;
    cursor: pointer;
}
 
#resetFilters {
    padding: 10px 20px;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    border: none;
    border-radius: 10px;
    font-size: 14px;
    font-weight: 700;
    cursor: pointer;
    transition: all 0.3s ease;
}
 
#resetFilters:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
 
/* ========== TABLES ========== */
.section {
    overflow-x: auto;
    background: white;
    border-radius: 12px;
    padding: 24px;
    margin-bottom: 20px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
    border: 1px solid #e0e4e8;
}
 
.section h2 {
    color: #212529;
    font-size: 20px;
    margin-bottom: 20px;
    font-weight: 800;
}
 
.section p {
    color: #666;
    font-size: 14px;
    line-height: 1.6;
}
 
table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px; /* ✅ CHANGEMENT: Réduit pour compacité */
}
 
th, td {
    padding: 12px 14px; /* ✅ CHANGEMENT: Padding réduit */
    text-align: left;
}
 
th {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    font-weight: 700;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    position: sticky;
    top: 0;
    z-index: 10;
}
 
/* ✅ ZEBRA STRIPING */
tr:nth-child(even) {
    background-color: #f8f9fa;
}
 
tr:nth-child(odd) {
    background-color: #ffffff;
}
 
tr:hover {
    background-color: #e3f2fd !important;
    transition: background-color 0.2s ease;
}
 
tr.matched td {
    background-color: #e8f5e9 !important;
    border-left: 4px solid #4caf50;
}
tr.matched td:last-child {
    border-right: 4px solid #4caf50;
}
 
tr.extra td {
    background-color: #e3f2fd !important;
    border-left: 4px solid #2196f3;
}
tr.extra td:last-child {
    border-right: 4px solid #2196f3;
}
 
tr.missing td {
    background-color: #ffebee !important;
    border-left: 4px solid #f44336;
}
tr.missing td:last-child {
    border-right: 4px solid #f44336;
}
 
.data-table td {
    padding: 12px 14px;
    border-bottom: 1px solid #e0e4e8;
    font-size: 13px;
    color: #333;
    font-weight: 500;
}
 
.data-table tr:last-child td {
    border-bottom: none;
}
 
/* ========== LIFECYCLE BADGES - COULEURS DIFFÉRENTES ========== */
/* ✅ CHANGEMENT: Couleurs très distinctes pour Deprecated et Preview */
 
tr.deprecated {
    background-color: #fff3e0 !important; /* Orange très clair */
}
 
tr.preview {
    background-color: #e8eaf6 !important; /* Indigo très clair - DIFFÉRENT */
}
 
tr.deprecated td {
    border-left: 4px solid #ff6f00; /* Orange foncé */
}
 
tr.preview td {
    border-left: 4px solid #3f51b5; /* Indigo - DIFFÉRENT */
}
 
.badge-deprecated {
    background: linear-gradient(135deg, #ff6f00 0%, #ff8f00 100%); /* Orange */
    color: white;
    padding: 5px 12px;
    border-radius: 14px;
    font-size: 11px;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    display: inline-block;
    box-shadow: 0 2px 6px rgba(255, 111, 0, 0.4);
}
 
.badge-preview {
    background: linear-gradient(135deg, #3f51b5 0%, #5c6bc0 100%); /* Indigo - DIFFÉRENT */
    color: white;
    padding: 5px 12px;
    border-radius: 14px;
    font-size: 11px;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    display: inline-block;
    box-shadow: 0 2px 6px rgba(63, 81, 181, 0.4);
}
 
.badge-deprecated::before { content: '⚠️ '; }
.badge-preview::before { content: '🧪 '; } /* ✅ CHANGEMENT: Icône différente */
 
/* ========== TABS ========== */
.tabs {
    background: white;
    border-radius: 12px;
    margin-bottom: 20px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
    overflow: hidden;
    border: 1px solid #e0e4e8;
}
 
.tab-header {
    display: flex;
    border-bottom: 2px solid #e0e4e8;
    background: #f8f9fa;
}
 
.tab-btn {
    flex: 1;
    padding: 16px 28px;
    background: transparent;
    border: none;
    cursor: pointer;
    font-size: 15px;
    font-weight: 700;
    color: #666;
    transition: all 0.3s ease;
    position: relative;
}
 
.tab-btn:hover {
    background: rgba(102, 126, 234, 0.05);
    color: #667eea;
}
 
.tab-btn.active {
    color: #667eea;
    background: white;
}
 
.tab-btn.active::after {
    content: '';
    position: absolute;
    bottom: -2px;
    left: 0;
    right: 0;
    height: 3px;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
 
.tab-content {
    display: none;
    padding: 0;
}
 
.tab-content.active {
    display: block;
}
 
/* ========== DETAILS/SUMMARY ========== */
details {
    margin-bottom: 20px;
}
 
details summary {
    cursor: pointer;
    padding: 16px 20px;
    background: #f8f9fa;
    border-radius: 10px;
    font-weight: 700;
    font-size: 15px;
    transition: all 0.3s ease;
    border: 2px solid #e0e4e8;
}
 
details summary:hover {
    background: #e9ecef;
    border-color: #667eea;
    color: #667eea;
}
 
details[open] {
    border: 2px solid #667eea;
    border-radius: 10px;
    padding: 10px;
}
 
details[open] summary {
    border-bottom: 2px solid #e0e4e8;
    margin-bottom: 14px;
    border-radius: 10px 10px 0 0;
}
 
/* ========== FOOTER ========== */
.footer {
    background: white;
    border-radius: 12px;
    padding: 20px;
    text-align: center;
    color: #666;
    font-size: 13px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
    border: 1px solid #e0e4e8;
    font-weight: 500;
}
 
.footer p {
    margin: 6px 0;
}
 
/* ========== RESPONSIVE DESIGN ========== */
@media (max-width: 1400px) {
    .container { max-width: 100%; }
}
 
@media (max-width: 768px) {
    body { padding: 12px; }
     
    .brand {
        flex-direction: column;
        gap: 14px;
        text-align: center;
    }
     
    .hero-score { padding: 32px 20px; }
     
    .score-value { font-size: 56px; }
     
    .score-cards { grid-template-columns: 1fr; }
     
    #filters {
        flex-direction: column;
        align-items: stretch;
    }
     
    #globalSearch,
    #statusFilter,
    #resetFilters {
        width: 100%;
    }
     
    table { font-size: 12px; }
     
    th, td { padding: 10px 12px; }
}
 
/* ========== PRINT STYLES (Éviter coupures de page) ========== */
@media print {
    body { background: white; }
     
    .section {
        page-break-inside: avoid;
    }
     
    table {
        page-break-inside: auto;
    }
     
    tr {
        page-break-inside: avoid;
        page-break-after: auto;
    }
}
</style>
"@

    
    $htmlContent = @"
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>$ProjectName - Policy Compliance Report</title>
    <link rel="stylesheet" href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css">
    $style
</head>
<body>
    <div class="container">
        <!-- Brand Header -->
        <div class="brand">
            <div style="display: flex; align-items: center; gap: 20px;">
                $(if ($logoDataUri) { "<img src='$logoDataUri' alt='Logo' class='brand-logo'>" } else { "" })
                <div class="brand-text">
                    <div class="brand-title">$ProjectName</div>
                    <div class="brand-sub">Azure Policy Compliance Analysis</div>
                </div>
            </div>
            <span class="version-badge">v$ProjectVersion</span>
        </div>
         
        <!-- Hero Score -->
        <div class="hero-score" $(if ($logoDataUri) { "data-has-logo='true' style='--logo-url: url($logoDataUri);'" } else { "" })>
            <div class="score-value">$([math]::Round($Scores.GlobalScore, 1))%</div>
            <div class="score-label">Compliance Score</div>
            <div class="score-badge">$complianceLevel</div>
            <div class="progress-bar-container">
                <div class="progress-bar" style="width: $($Scores.GlobalScore)%;"></div>
            </div>
            <div class="progress-text">$totalMatched / $totalBaseline policies</div>
            <div class="metrics-pills">
                <div class="pill matched"><span class="num">$totalMatched</span> Match</div>
                <div class="pill missing"><span class="num">$totalMissing</span> Missing</div>
                <div class="pill version"><span class="num">$totalVersionMismatch</span> Version Mismatch</div>
                $(if ($extraFilteredOut) {
    '<div class="pill extra" style="opacity: 0.5;"><span class="num">-</span> Extra (filtered)</div>'
} else {
    "<div class='pill extra'><span class='num'>$totalExtra</span> Extra</div>"
})
            </div>
        </div>
         
        <!-- Score Cards -->
        <div class="score-cards">
            <div class="score-card alz">
                <div class="card-header">
                    <span>🏛️ AZURE LANDING ZONES</span>
                </div>
                <div class="card-score">$([math]::Round($Scores.AlzScore, 1))%</div>
                <div class="card-subtitle">$alzDeployed / $alzBaselineCount policies</div>
                <div class="card-progress">
                    <div class="card-progress-bar alz" style="width: $($Scores.AlzScore)%;"></div>
                </div>
                <div class="card-stats">
                    <span>✅ Matched: $alzDeployed</span>
                    <span>❌ Missing: $alzMissing</span>
                </div>
            </div>
             
            <div class="score-card mcsb">
                <div class="card-header">
                    <span>🔒 MICROSOFT CLOUD SECURITY BENCHMARK</span>
                </div>
                <div class="card-score">$([math]::Round($Scores.McsbScore, 1))%</div>
                <div class="card-subtitle">$mcsbDeployed / $mcsbBaselineCount policies</div>
                <div class="card-progress">
                    <div class="card-progress-bar mcsb" style="width: $($Scores.McsbScore)%;"></div>
                </div>
                <div class="card-stats">
                    <span>✅ Matched: $mcsbDeployed</span>
                    <span>❌ Missing: $mcsbMissing</span>
                </div>
            </div>
        </div>
         
        <!-- Filters -->
        <div id="filters">
            <input type="text" id="globalSearch" placeholder="Search (name / id / text)...">
<select id="statusFilter">
    <option value="Tous">All</option>
    <option value="Matched">Matched</option>
    <option value="Missing">Missing</option>
    <option value="Extra">Extra</option>
</select>
            <label>
                <input type="checkbox" id="chkDeprecated"> Hide Deprecated
            </label>
            <label>
                <input type="checkbox" id="chkPreview"> Hide Preview
            </label>
            <button id="resetFilters">Reset</button>
        </div>
         
        <!-- Tabs Container -->
        <div class="tabs">
            <!-- Tab Header -->
            <div class="tab-header">
<button class="tab-btn active" data-tab="environment">🏢 My Environment</button>
<button class="tab-btn" data-tab="recommendations">📋 Recommendations</button>
            </div>
             
            <!-- Tab Content: Mon environnement -->
            <div class="tab-content active" id="environment">
                <!-- Custom Initiatives Section -->
                <details class="custom-initiatives" open>
                    <summary>📊 Custom Initiatives - Click to collapse</summary>
                    <div class="section">
                        $sumCustomHtml
                    </div>
                </details>
                 
                <!-- Individual Policies Section -->
                <details class="individual-policies" open>
                    <summary>📌 Individual Policies - Click to collapse</summary>
                    <div class="section">
                        $sumIndividualHtml
                    </div>
                </details>
            </div>
             
            <!-- Tab Content: Recommandations -->
            <div class="tab-content" id="recommendations">
                <!-- MCSB Deployment Section -->
                <details open>
                    <summary>🔒 MCSB Deployment - Click to expand</summary>
                    <div class="section">
                        <h2>🔒 MCSB Initiatives Summary</h2>
                        $sumMcsbHtml
                        <h2 style="margin-top: 30px;">🎯 MCSB Baseline Details</h2>
                        $mcsbHtml
                    </div>
                </details>
                 
                <!-- ALZ Baseline Section -->
                <details open>
                    <summary>🚀 ALZ Baseline - Click to expand</summary>
                    <div class="section">
                        <h2>🚀 ALZ Baseline Details</h2>
                        $alzHtml
                    </div>
                </details>
            </div>
        </div>
         
        <!-- Footer -->
        <div class="footer">
            <p>Generated on $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') | Scope: $Scope</p>
            <p>$ProjectName v$ProjectVersion | Azure Policy Compliance Analysis</p>
        </div>
    </div>
     
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
    <script>
    `$(document).ready(function(){
        // Find status column index dynamically by header name
        function findColumnIndex(table, columnName) {
            var colIndex = -1;
            table.find('thead tr th, tr:first th, tr:first td').each(function(idx) {
                var headerText = `$(this).text().trim().toUpperCase();
                if (headerText === columnName.toUpperCase()) {
                    colIndex = idx;
                    return false;
                }
            });
            return colIndex;
        }
         
        // Apply coloring and classes to tables
        var tableIds = ['tblSummaryCustom', 'tblSummaryMcsb', 'tblIndividual', 'tblMCSB', 'tblALZ'];
         
        tableIds.forEach(function(tableId){
            var tbl = `$('#' + tableId);
            if(tbl.length){
                // Find AssignmentStatus column dynamically
                var statusColIndex = findColumnIndex(tbl, 'ASSIGNMENTSTATUS');
                 
                if (statusColIndex === -1) {
                    console.log(tableId + ': AssignmentStatus column not found, trying index 5');
                    statusColIndex = 5;
                }
                 
                console.log(tableId + ': Using status column index ' + statusColIndex);
                 
                var rowCount = 0;
                var matchedCount = 0;
                var extraCount = 0;
                var missingCount = 0;
                 
                // Iterate all data rows
                tbl.find('tr').each(function(idx){
                    var row = `$(this);
                    var cells = row.find('td');
                     
                    // Skip header rows
                    if(cells.length === 0) return;
                     
                    rowCount++;
                    var statusCell = cells.eq(statusColIndex);
                    var status = statusCell.text().trim();
                     
                    console.log('Row ' + idx + ': status = "' + status + '"');
                     
                    // Apply row classes based on status
                    if(status === 'Matched'){
                        row.addClass('matched');
                        matchedCount++;
                    } else if(status === 'Extra'){
                        row.addClass('extra');
                        extraCount++;
                    } else if(status === 'Missing'){
                        row.addClass('missing');
                        missingCount++;
                    }
                     
                    // Check for Deprecated/Preview in all cells
                    cells.each(function(){
                        var txt = `$(this).text();
                        if(txt.includes('Deprecated')){
                            row.addClass('deprecated');
                            `$(this).html(txt.replace('Deprecated','<span class="badge-deprecated">Deprecated</span>'));
                        }
                        if(txt.includes('Preview')){
                            row.addClass('preview');
                            `$(this).html(txt.replace('Preview','<span class="badge-preview">Preview</span>'));
                        }
                    });
                });
                 
                console.log(tableId + ': ' + rowCount + ' rows - ' + matchedCount + ' matched, ' + extraCount + ' extra, ' + missingCount + ' missing');
            }
        });
         
        // Global filters
        function applyFiltersAllTables(){
            var searchVal = `$('#globalSearch').val().toLowerCase();
            var statusVal = `$('#statusFilter').val();
            var hideDeprecated = `$('#chkDeprecated').is(':checked');
            var hidePreview = `$('#chkPreview').is(':checked');
             
            console.log('Filter applied: status=' + statusVal);
             
            `$('table tr').each(function(){
                var row = `$(this);
                 
                // Skip header rows
                if (row.find('th').length > 0) return;
                if (row.find('td').length === 0) return;
                 
                var show = true;
                 
                // Text search
                if(searchVal){
                    var rowText = row.text().toLowerCase();
                    if(rowText.indexOf(searchVal) === -1) show = false;
                }
                 
                // Status filter
                if(show && statusVal !== 'Tous'){
                    var hasClass = row.hasClass(statusVal.toLowerCase());
                    console.log('Row classes: ' + row.attr('class') + ', looking for: ' + statusVal.toLowerCase() + ', hasClass: ' + hasClass);
                    if(!hasClass) show = false;
                }
                 
                // Deprecated filter
                if(show && hideDeprecated && row.hasClass('deprecated')) show = false;
                 
                // Preview filter
                if(show && hidePreview && row.hasClass('preview')) show = false;
                 
                row.toggle(show);
            });
        }
         
        `$('#globalSearch').on('keyup', applyFiltersAllTables);
        `$('#statusFilter').on('change', applyFiltersAllTables);
        `$('#chkDeprecated, #chkPreview').on('change', applyFiltersAllTables);
        `$('#resetFilters').on('click', function(){
            `$('#globalSearch').val('');
            `$('#statusFilter').val('Tous');
            `$('#chkDeprecated, #chkPreview').prop('checked', false);
            applyFiltersAllTables();
        });
         
        // Tab switching
        `$('.tab-btn').on('click', function(){
            var targetTab = `$(this).data('tab');
             
            `$('.tab-btn').removeClass('active');
            `$(this).addClass('active');
             
            `$('.tab-content').removeClass('active');
            `$('#' + targetTab).addClass('active');
        });
    });
    </script>
    </script>
</body>
</html>
"@

    
    try {
        $htmlContent | Out-File -FilePath $OutputPath -Encoding UTF8
        Write-Host (" ✅ HTML report generated: {0}" -f (Split-Path $OutputPath -Leaf)) -ForegroundColor Green
    }
    catch {
        Write-Warning "Failed to export HTML report: $($_.Exception.Message)"
    }
}