---
phase: 08-simplified-permissions
plan: 04
type: execute
wave: 3
depends_on: ["08-01"]
files_modified:
  - SharepointToolbox/Services/Export/HtmlExportService.cs
  - SharepointToolbox/Services/Export/CsvExportService.cs
autonomous: true
requirements:
  - SIMP-01
  - SIMP-02
must_haves:
  truths:
    - "HTML export includes a Simplified Labels column and color-coded permission cells when simplified entries are provided"
    - "HTML summary section shows risk level counts with color indicators"
    - "CSV export includes a Simplified Labels column after the raw Permission Levels column"
    - "Both export services accept SimplifiedPermissionEntry via overloaded methods — original PermissionEntry methods remain unchanged"
  artifacts:
    - path: "SharepointToolbox/Services/Export/HtmlExportService.cs"
      provides: "HTML export with simplified labels and risk-level color coding"
      contains: "BuildHtml.*SimplifiedPermissionEntry"
    - path: "SharepointToolbox/Services/Export/CsvExportService.cs"
      provides: "CSV export with simplified labels column"
      contains: "BuildCsv.*SimplifiedPermissionEntry"
  key_links:
    - from: "SharepointToolbox/Services/Export/HtmlExportService.cs"
      to: "SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs"
      via: "Overloaded BuildHtml and WriteAsync methods"
      pattern: "SimplifiedPermissionEntry"
    - from: "SharepointToolbox/Services/Export/CsvExportService.cs"
      to: "SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs"
      via: "Overloaded BuildCsv and WriteAsync methods"
      pattern: "SimplifiedPermissionEntry"
---

<objective>
Add simplified-mode export support to HtmlExportService and CsvExportService. Both services get new overloaded methods that accept SimplifiedPermissionEntry and include plain-language labels and risk-level color coding. Original PermissionEntry methods are NOT modified.

Purpose: Exports reflect the simplified view (SIMP-01 labels, SIMP-02 colors) so exported reports match what the user sees in the UI.
Output: Updated HtmlExportService.cs, Updated CsvExportService.cs
</objective>

<execution_context>
@C:/Users/dev/.claude/get-shit-done/workflows/execute-plan.md
@C:/Users/dev/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/phases/08-simplified-permissions/08-01-SUMMARY.md

<interfaces>
<!-- From 08-01: Types used by export services -->
From SharepointToolbox/Core/Models/RiskLevel.cs:
```csharp
public enum RiskLevel { High, Medium, Low, ReadOnly }
```

From SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs:
```csharp
public class SimplifiedPermissionEntry
{
    public PermissionEntry Inner { get; }
    public string SimplifiedLabels { get; }
    public RiskLevel RiskLevel { get; }
    // Passthrough: ObjectType, Title, Url, HasUniquePermissions, Users, UserLogins,
    //              PermissionLevels, GrantedThrough, PrincipalType
}
```

From SharepointToolbox/Core/Models/PermissionSummary.cs:
```csharp
public record PermissionSummary(string Label, RiskLevel RiskLevel, int Count, int DistinctUsers);
public static class PermissionSummaryBuilder
{
    public static IReadOnlyList<PermissionSummary> Build(IEnumerable<SimplifiedPermissionEntry> entries);
}
```

<!-- Current export service signatures -->
From SharepointToolbox/Services/Export/CsvExportService.cs:
```csharp
public class CsvExportService
{
    public string BuildCsv(IReadOnlyList<PermissionEntry> entries);
    public async Task WriteAsync(IReadOnlyList<PermissionEntry> entries, string filePath, CancellationToken ct);
}
```

From SharepointToolbox/Services/Export/HtmlExportService.cs:
```csharp
public class HtmlExportService
{
    public string BuildHtml(IReadOnlyList<PermissionEntry> entries);
    public async Task WriteAsync(IReadOnlyList<PermissionEntry> entries, string filePath, CancellationToken ct);
}
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add simplified export overloads to CsvExportService</name>
  <files>SharepointToolbox/Services/Export/CsvExportService.cs</files>
  <action>
    Modify `SharepointToolbox/Services/Export/CsvExportService.cs`. Add `using SharepointToolbox.Core.Models;` if not already present (it is). Keep ALL existing methods unchanged. Add these new overloaded methods:

    ```csharp
    /// <summary>
    /// Header for simplified CSV export — includes "SimplifiedLabels" and "RiskLevel" columns.
    /// </summary>
    private const string SimplifiedHeader =
        "\"Object\",\"Title\",\"URL\",\"HasUniquePermissions\",\"Users\",\"UserLogins\",\"Type\",\"Permissions\",\"SimplifiedLabels\",\"RiskLevel\",\"GrantedThrough\"";

    /// <summary>
    /// Builds a CSV string from simplified permission entries.
    /// Includes SimplifiedLabels and RiskLevel columns after raw Permissions.
    /// Uses the same merge logic as the standard BuildCsv.
    /// </summary>
    public string BuildCsv(IReadOnlyList<SimplifiedPermissionEntry> entries)
    {
        var sb = new StringBuilder();
        sb.AppendLine(SimplifiedHeader);

        var merged = entries
            .GroupBy(e => (e.Users, e.PermissionLevels, e.GrantedThrough))
            .Select(g => new
            {
                ObjectType     = g.First().ObjectType,
                Title          = string.Join(" | ", g.Select(e => e.Title).Distinct()),
                Url            = string.Join(" | ", g.Select(e => e.Url).Distinct()),
                HasUnique      = g.First().HasUniquePermissions,
                Users          = g.Key.Users,
                UserLogins     = g.First().UserLogins,
                PrincipalType  = g.First().PrincipalType,
                Permissions    = g.Key.PermissionLevels,
                SimplifiedLabels = g.First().SimplifiedLabels,
                RiskLevel      = g.First().RiskLevel.ToString(),
                GrantedThrough = g.Key.GrantedThrough
            });

        foreach (var row in merged)
            sb.AppendLine(string.Join(",", new[]
            {
                Csv(row.ObjectType), Csv(row.Title), Csv(row.Url),
                Csv(row.HasUnique.ToString()), Csv(row.Users), Csv(row.UserLogins),
                Csv(row.PrincipalType), Csv(row.Permissions), Csv(row.SimplifiedLabels),
                Csv(row.RiskLevel), Csv(row.GrantedThrough)
            }));

        return sb.ToString();
    }

    /// <summary>
    /// Writes simplified CSV to the specified file path.
    /// </summary>
    public async Task WriteAsync(IReadOnlyList<SimplifiedPermissionEntry> entries, string filePath, CancellationToken ct)
    {
        var csv = BuildCsv(entries);
        await File.WriteAllTextAsync(filePath, csv, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true), ct);
    }
    ```

    Do NOT modify the existing `BuildCsv(IReadOnlyList<PermissionEntry>)` or `WriteAsync(IReadOnlyList<PermissionEntry>, ...)` methods. The new methods are overloads (same name, different parameter type).
  </action>
  <verify>
    <automated>cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-incremental 2>&1 | tail -5</automated>
  </verify>
  <done>CsvExportService has overloaded BuildCsv and WriteAsync accepting SimplifiedPermissionEntry. CSV includes SimplifiedLabels and RiskLevel columns. Original PermissionEntry methods unchanged.</done>
</task>

<task type="auto">
  <name>Task 2: Add simplified export overloads to HtmlExportService</name>
  <files>SharepointToolbox/Services/Export/HtmlExportService.cs</files>
  <action>
    Modify `SharepointToolbox/Services/Export/HtmlExportService.cs`. Keep ALL existing methods unchanged. Add these new overloaded methods and helpers:

    Add to the class a risk-level-to-CSS-color mapping method:

    ```csharp
    /// <summary>Returns inline CSS background and text color for a risk level.</summary>
    private static (string bg, string text, string border) RiskLevelColors(RiskLevel level) => level switch
    {
        RiskLevel.High     => ("#FEE2E2", "#991B1B", "#FECACA"),
        RiskLevel.Medium   => ("#FEF3C7", "#92400E", "#FDE68A"),
        RiskLevel.Low      => ("#D1FAE5", "#065F46", "#A7F3D0"),
        RiskLevel.ReadOnly => ("#DBEAFE", "#1E40AF", "#BFDBFE"),
        _                  => ("#F3F4F6", "#374151", "#E5E7EB")
    };
    ```

    Add the simplified BuildHtml overload. This is a full method — include the complete implementation. It extends the existing HTML template with:
    - Risk-level summary cards (instead of just stats)
    - A "Simplified Labels" column in the table
    - Color-coded risk badges on each row

    ```csharp
    /// <summary>
    /// Builds a self-contained HTML string from simplified permission entries.
    /// Includes risk-level summary cards, color-coded rows, and simplified labels column.
    /// </summary>
    public string BuildHtml(IReadOnlyList<SimplifiedPermissionEntry> entries)
    {
        var summaries = PermissionSummaryBuilder.Build(entries);

        var totalEntries   = entries.Count;
        var uniquePermSets = entries.Select(e => e.PermissionLevels).Distinct().Count();
        var distinctUsers  = entries
            .SelectMany(e => e.UserLogins.Split(';', StringSplitOptions.RemoveEmptyEntries))
            .Select(u => u.Trim())
            .Where(u => u.Length > 0)
            .Distinct()
            .Count();

        var sb = new StringBuilder();

        sb.AppendLine("<!DOCTYPE html>");
        sb.AppendLine("<html lang=\"en\">");
        sb.AppendLine("<head>");
        sb.AppendLine("<meta charset=\"UTF-8\">");
        sb.AppendLine("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">");
        sb.AppendLine("<title>SharePoint Permissions Report (Simplified)</title>");
        sb.AppendLine("<style>");
        sb.AppendLine(@"
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; }
    h1 { padding: 20px 24px 10px; font-size: 1.5rem; color: #1a1a2e; }
    .stats { display: flex; gap: 16px; padding: 0 24px 16px; flex-wrap: wrap; }
    .stat-card { background: #fff; border-radius: 8px; padding: 14px 20px; min-width: 160px; box-shadow: 0 1px 4px rgba(0,0,0,.1); }
    .stat-card .value { font-size: 2rem; font-weight: 700; color: #1a1a2e; }
    .stat-card .label { font-size: .8rem; color: #666; margin-top: 2px; }
    .risk-cards { display: flex; gap: 12px; padding: 0 24px 16px; flex-wrap: wrap; }
    .risk-card { border-radius: 8px; padding: 12px 18px; min-width: 140px; border: 1px solid; }
    .risk-card .count { font-size: 1.5rem; font-weight: 700; }
    .risk-card .rlabel { font-size: .8rem; margin-top: 2px; }
    .risk-card .users { font-size: .7rem; margin-top: 2px; opacity: 0.8; }
    .filter-wrap { padding: 0 24px 12px; }
    #filter { width: 320px; padding: 8px 12px; border: 1px solid #ccc; border-radius: 6px; font-size: .95rem; }
    .table-wrap { overflow-x: auto; padding: 0 24px 32px; }
    table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 4px rgba(0,0,0,.1); }
    th { background: #1a1a2e; color: #fff; padding: 10px 14px; text-align: left; font-size: .85rem; white-space: nowrap; }
    td { padding: 9px 14px; border-bottom: 1px solid #eee; vertical-align: top; font-size: .875rem; }
    tr:last-child td { border-bottom: none; }
    tr:hover td { background: rgba(0,0,0,.03); }
    .badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: .75rem; font-weight: 600; white-space: nowrap; }
    .badge.site-coll { background: #dbeafe; color: #1e40af; }
    .badge.site      { background: #dcfce7; color: #166534; }
    .badge.list      { background: #fef9c3; color: #854d0e; }
    .badge.folder    { background: #f3f4f6; color: #374151; }
    .badge.unique    { background: #dcfce7; color: #166534; }
    .badge.inherited { background: #f3f4f6; color: #374151; }
    .risk-badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: .75rem; font-weight: 600; border: 1px solid; }
    .user-pill { display: inline-block; background: #e0e7ff; color: #3730a3; border-radius: 12px; padding: 2px 10px; font-size: .75rem; margin: 2px 3px 2px 0; white-space: nowrap; }
    .user-pill.external-user { background: #fff7ed; color: #9a3412; border: 1px solid #fed7aa; }
    a { color: #2563eb; text-decoration: none; }
    a:hover { text-decoration: underline; }
    ");
        sb.AppendLine("</style>");
        sb.AppendLine("</head>");

        sb.AppendLine("<body>");
        sb.AppendLine("<h1>SharePoint Permissions Report (Simplified)</h1>");

        // Stats cards
        sb.AppendLine("<div class=\"stats\">");
        sb.AppendLine($"  <div class=\"stat-card\"><div class=\"value\">{totalEntries}</div><div class=\"label\">Total Entries</div></div>");
        sb.AppendLine($"  <div class=\"stat-card\"><div class=\"value\">{uniquePermSets}</div><div class=\"label\">Unique Permission Sets</div></div>");
        sb.AppendLine($"  <div class=\"stat-card\"><div class=\"value\">{distinctUsers}</div><div class=\"label\">Distinct Users/Groups</div></div>");
        sb.AppendLine("</div>");

        // Risk-level summary cards
        sb.AppendLine("<div class=\"risk-cards\">");
        foreach (var summary in summaries)
        {
            var (bg, text, border) = RiskLevelColors(summary.RiskLevel);
            sb.AppendLine($"  <div class=\"risk-card\" style=\"background:{bg};color:{text};border-color:{border}\">");
            sb.AppendLine($"    <div class=\"count\">{summary.Count}</div>");
            sb.AppendLine($"    <div class=\"rlabel\">{HtmlEncode(summary.Label)}</div>");
            sb.AppendLine($"    <div class=\"users\">{summary.DistinctUsers} user(s)</div>");
            sb.AppendLine("  </div>");
        }
        sb.AppendLine("</div>");

        // Filter input
        sb.AppendLine("<div class=\"filter-wrap\">");
        sb.AppendLine("  <input type=\"text\" id=\"filter\" placeholder=\"Filter permissions...\" onkeyup=\"filterTable()\" />");
        sb.AppendLine("</div>");

        // Table with simplified columns
        sb.AppendLine("<div class=\"table-wrap\">");
        sb.AppendLine("<table id=\"permTable\">");
        sb.AppendLine("<thead><tr>");
        sb.AppendLine("  <th>Object</th><th>Title</th><th>URL</th><th>Unique</th><th>Users/Groups</th><th>Permission Level</th><th>Simplified</th><th>Risk</th><th>Granted Through</th>");
        sb.AppendLine("</tr></thead>");
        sb.AppendLine("<tbody>");

        foreach (var entry in entries)
        {
            var typeCss   = ObjectTypeCss(entry.ObjectType);
            var uniqueCss = entry.HasUniquePermissions ? "badge unique" : "badge inherited";
            var uniqueLbl = entry.HasUniquePermissions ? "Unique" : "Inherited";
            var (riskBg, riskText, riskBorder) = RiskLevelColors(entry.RiskLevel);

            var logins = entry.UserLogins.Split(';', StringSplitOptions.RemoveEmptyEntries);
            var names  = entry.Inner.Users.Split(';', StringSplitOptions.RemoveEmptyEntries);
            var pillsBuilder = new StringBuilder();
            for (int i = 0; i < logins.Length; i++)
            {
                var login = logins[i].Trim();
                var name  = i < names.Length ? names[i].Trim() : login;
                var isExt = login.Contains("#EXT#", StringComparison.OrdinalIgnoreCase);
                var pillCss = isExt ? "user-pill external-user" : "user-pill";
                pillsBuilder.Append($"<span class=\"{pillCss}\" data-email=\"{HtmlEncode(login)}\">{HtmlEncode(name)}</span>");
            }

            sb.AppendLine("<tr>");
            sb.AppendLine($"  <td><span class=\"{typeCss}\">{HtmlEncode(entry.ObjectType)}</span></td>");
            sb.AppendLine($"  <td>{HtmlEncode(entry.Title)}</td>");
            sb.AppendLine($"  <td><a href=\"{HtmlEncode(entry.Url)}\" target=\"_blank\">Link</a></td>");
            sb.AppendLine($"  <td><span class=\"{uniqueCss}\">{uniqueLbl}</span></td>");
            sb.AppendLine($"  <td>{pillsBuilder}</td>");
            sb.AppendLine($"  <td>{HtmlEncode(entry.PermissionLevels)}</td>");
            sb.AppendLine($"  <td>{HtmlEncode(entry.SimplifiedLabels)}</td>");
            sb.AppendLine($"  <td><span class=\"risk-badge\" style=\"background:{riskBg};color:{riskText};border-color:{riskBorder}\">{HtmlEncode(entry.RiskLevel.ToString())}</span></td>");
            sb.AppendLine($"  <td>{HtmlEncode(entry.GrantedThrough)}</td>");
            sb.AppendLine("</tr>");
        }

        sb.AppendLine("</tbody>");
        sb.AppendLine("</table>");
        sb.AppendLine("</div>");

        sb.AppendLine("<script>");
        sb.AppendLine(@"function filterTable() {
      var input = document.getElementById('filter').value.toLowerCase();
      var rows = document.querySelectorAll('#permTable tbody tr');
      rows.forEach(function(row) {
        row.style.display = row.textContent.toLowerCase().indexOf(input) > -1 ? '' : 'none';
      });
    }");
        sb.AppendLine("</script>");
        sb.AppendLine("</body>");
        sb.AppendLine("</html>");

        return sb.ToString();
    }

    /// <summary>
    /// Writes the simplified HTML report to the specified file path.
    /// </summary>
    public async Task WriteAsync(IReadOnlyList<SimplifiedPermissionEntry> entries, string filePath, CancellationToken ct)
    {
        var html = BuildHtml(entries);
        await File.WriteAllTextAsync(filePath, html, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), ct);
    }
    ```

    Add the required using statements at the top of the file:
    ```csharp
    using SharepointToolbox.Core.Models;  // Already present
    ```
    Note: PermissionSummaryBuilder is in the SharepointToolbox.Core.Models namespace so no additional using is needed.

    Do NOT modify the existing `BuildHtml(IReadOnlyList<PermissionEntry>)` or `WriteAsync(IReadOnlyList<PermissionEntry>, ...)` methods. The new methods are overloads.
  </action>
  <verify>
    <automated>cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-incremental 2>&1 | tail -5</automated>
  </verify>
  <done>HtmlExportService has overloaded BuildHtml and WriteAsync accepting SimplifiedPermissionEntry. HTML includes risk-level summary cards, Simplified column, and color-coded Risk badges. CsvExportService has overloaded methods with SimplifiedLabels and RiskLevel columns. Original methods for PermissionEntry remain unchanged.</done>
</task>

</tasks>

<verification>
- `dotnet build SharepointToolbox/SharepointToolbox.csproj` succeeds with 0 errors
- HtmlExportService has both `BuildHtml(IReadOnlyList<PermissionEntry>)` and `BuildHtml(IReadOnlyList<SimplifiedPermissionEntry>)`
- CsvExportService has both `BuildCsv(IReadOnlyList<PermissionEntry>)` and `BuildCsv(IReadOnlyList<SimplifiedPermissionEntry>)`
- Simplified HTML output includes risk-card section and Risk column
- Simplified CSV output includes SimplifiedLabels and RiskLevel headers
</verification>

<success_criteria>
Both export services support simplified mode. The PermissionsViewModel export commands (which will be updated to pass SimplifiedResults when IsSimplifiedMode is true — wired in plan 08-05) can produce exports that match the simplified UI view. Original export paths for non-simplified mode remain untouched.
</success_criteria>

<output>
After completion, create `.planning/phases/08-simplified-permissions/08-04-SUMMARY.md`
</output>
