---
phase: 08-simplified-permissions
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - SharepointToolbox/Core/Models/RiskLevel.cs
  - SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs
  - SharepointToolbox/Core/Models/PermissionSummary.cs
  - SharepointToolbox/Core/Helpers/PermissionLevelMapping.cs
autonomous: true
requirements:
  - SIMP-01
  - SIMP-02
must_haves:
  truths:
    - "RiskLevel enum distinguishes High, Medium, Low, and ReadOnly access tiers"
    - "PermissionLevelMapping maps all standard SharePoint role names to plain-language labels and risk levels"
    - "SimplifiedPermissionEntry wraps PermissionEntry with computed simplified labels and risk level without modifying the original record"
    - "PermissionSummary groups permission entries by risk level with counts"
    - "Unknown/custom role names fall back to the raw name with a Medium risk level"
  artifacts:
    - path: "SharepointToolbox/Core/Models/RiskLevel.cs"
      provides: "Risk level classification enum"
      contains: "enum RiskLevel"
    - path: "SharepointToolbox/Core/Helpers/PermissionLevelMapping.cs"
      provides: "Static mapping from SP role names to plain-language labels"
      contains: "class PermissionLevelMapping"
    - path: "SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs"
      provides: "Presentation wrapper for PermissionEntry with simplified fields"
      contains: "class SimplifiedPermissionEntry"
    - path: "SharepointToolbox/Core/Models/PermissionSummary.cs"
      provides: "Aggregation model for summary counts by risk level"
      contains: "record PermissionSummary"
  key_links:
    - from: "SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs"
      to: "SharepointToolbox/Core/Helpers/PermissionLevelMapping.cs"
      via: "Static method call to resolve labels and risk level"
      pattern: "PermissionLevelMapping\\.Get"
    - from: "SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs"
      to: "SharepointToolbox/Core/Models/PermissionEntry.cs"
      via: "Wraps original entry as Inner property"
      pattern: "PermissionEntry Inner"
---

<objective>
Define the data models and mapping layer for simplified permissions: RiskLevel enum, PermissionLevelMapping helper, SimplifiedPermissionEntry wrapper, and PermissionSummary aggregation model.

Purpose: All subsequent plans import these types. The mapping layer is the core of SIMP-01 (plain-language labels) and SIMP-02 (risk level color coding). PermissionEntry is immutable and NOT modified — SimplifiedPermissionEntry wraps it as a presentation concern.
Output: RiskLevel.cs, PermissionLevelMapping.cs, SimplifiedPermissionEntry.cs, PermissionSummary.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/STATE.md

<interfaces>
<!-- PermissionEntry is READ-ONLY — do NOT modify this record -->
From SharepointToolbox/Core/Models/PermissionEntry.cs:
```csharp
namespace SharepointToolbox.Core.Models;

public record PermissionEntry(
    string ObjectType,          // "Site Collection" | "Site" | "List" | "Folder"
    string Title,
    string Url,
    bool   HasUniquePermissions,
    string Users,               // Semicolon-joined display names
    string UserLogins,          // Semicolon-joined login names
    string PermissionLevels,    // Semicolon-joined role names (Limited Access already removed)
    string GrantedThrough,      // "Direct Permissions" | "SharePoint Group: <name>"
    string PrincipalType        // "SharePointGroup" | "User" | "External User"
);
```

From SharepointToolbox/Core/Helpers/PermissionEntryHelper.cs:
```csharp
public static class PermissionEntryHelper
{
    public static bool IsExternalUser(string loginName);
    public static IReadOnlyList<string> FilterPermissionLevels(IEnumerable<string> levels);
    public static bool IsSharingLinksGroup(string loginName);
}
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create RiskLevel enum and PermissionLevelMapping helper</name>
  <files>SharepointToolbox/Core/Models/RiskLevel.cs, SharepointToolbox/Core/Helpers/PermissionLevelMapping.cs</files>
  <action>
    Create `SharepointToolbox/Core/Models/RiskLevel.cs`:

    ```csharp
    namespace SharepointToolbox.Core.Models;

    /// <summary>
    /// Classifies a SharePoint permission level by its access risk.
    /// Used for color coding in both WPF DataGrid and HTML export.
    /// </summary>
    public enum RiskLevel
    {
        /// <summary>Full Control, Site Collection Administrator — can delete site, manage permissions.</summary>
        High,
        /// <summary>Contribute, Edit, Design — can modify content.</summary>
        Medium,
        /// <summary>Read, Restricted View — can view but not modify.</summary>
        Low,
        /// <summary>View Only — most restricted legitimate access.</summary>
        ReadOnly
    }
    ```

    Create `SharepointToolbox/Core/Helpers/PermissionLevelMapping.cs`:

    ```csharp
    using SharepointToolbox.Core.Models;

    namespace SharepointToolbox.Core.Helpers;

    /// <summary>
    /// Maps SharePoint built-in permission level names to human-readable labels and risk levels.
    /// Used by SimplifiedPermissionEntry and export services to translate raw role names
    /// into plain-language descriptions that non-technical users can understand.
    /// </summary>
    public static class PermissionLevelMapping
    {
        /// <summary>
        /// Result of looking up a SharePoint role name.
        /// </summary>
        public record MappingResult(string Label, RiskLevel RiskLevel);

        /// <summary>
        /// Known SharePoint built-in permission level mappings.
        /// Keys are case-insensitive via the dictionary comparer.
        /// </summary>
        private static readonly Dictionary<string, MappingResult> Mappings = new(StringComparer.OrdinalIgnoreCase)
        {
            // High risk — full administrative access
            ["Full Control"]                 = new("Full control (can manage everything)", RiskLevel.High),
            ["Site Collection Administrator"] = new("Site collection admin (full control)", RiskLevel.High),

            // Medium risk — can modify content
            ["Contribute"]       = new("Can edit files and list items", RiskLevel.Medium),
            ["Edit"]             = new("Can edit files, lists, and pages", RiskLevel.Medium),
            ["Design"]           = new("Can edit pages and use design tools", RiskLevel.Medium),
            ["Approve"]          = new("Can approve content and list items", RiskLevel.Medium),
            ["Manage Hierarchy"] = new("Can create sites and manage pages", RiskLevel.Medium),

            // Low risk — read access
            ["Read"]             = new("Can view files and pages", RiskLevel.Low),
            ["Restricted Read"]  = new("Can view pages only (no download)", RiskLevel.Low),

            // Read-only — most restricted
            ["View Only"]        = new("Can view files in browser only", RiskLevel.ReadOnly),
            ["Restricted View"]  = new("Restricted view access", RiskLevel.ReadOnly),
        };

        /// <summary>
        /// Gets the human-readable label and risk level for a SharePoint role name.
        /// Returns the mapped result for known roles; for unknown/custom roles,
        /// returns the raw name as-is with Medium risk level.
        /// </summary>
        public static MappingResult GetMapping(string roleName)
        {
            if (string.IsNullOrWhiteSpace(roleName))
                return new MappingResult(roleName, RiskLevel.Low);

            return Mappings.TryGetValue(roleName.Trim(), out var result)
                ? result
                : new MappingResult(roleName.Trim(), RiskLevel.Medium);
        }

        /// <summary>
        /// Resolves a semicolon-delimited PermissionLevels string into individual mapping results.
        /// This handles the PermissionEntry.PermissionLevels format (e.g. "Full Control; Contribute").
        /// </summary>
        public static IReadOnlyList<MappingResult> GetMappings(string permissionLevels)
        {
            if (string.IsNullOrWhiteSpace(permissionLevels))
                return Array.Empty<MappingResult>();

            return permissionLevels
                .Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
                .Select(GetMapping)
                .ToList();
        }

        /// <summary>
        /// Returns the highest (most dangerous) risk level from a semicolon-delimited permission levels string.
        /// Used for row-level color coding when an entry has multiple roles.
        /// </summary>
        public static RiskLevel GetHighestRisk(string permissionLevels)
        {
            var mappings = GetMappings(permissionLevels);
            if (mappings.Count == 0) return RiskLevel.Low;

            // High < Medium < Low < ReadOnly in enum order — Min gives highest risk
            return mappings.Min(m => m.RiskLevel);
        }

        /// <summary>
        /// Converts a semicolon-delimited PermissionLevels string into a simplified labels string.
        /// E.g. "Full Control; Contribute" becomes "Full control (can manage everything); Can edit files and list items"
        /// </summary>
        public static string GetSimplifiedLabels(string permissionLevels)
        {
            var mappings = GetMappings(permissionLevels);
            return string.Join("; ", mappings.Select(m => m.Label));
        }
    }
    ```

    Design notes:
    - Case-insensitive lookup handles variations in SharePoint role name casing
    - Unknown/custom roles default to Medium (conservative — forces admin review)
    - GetHighestRisk uses enum ordering (High=0 is most dangerous) for row-level color
    - Semicolon-split methods handle the PermissionEntry.PermissionLevels format directly
  </action>
  <verify>
    <automated>cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-incremental 2>&1 | tail -5</automated>
  </verify>
  <done>RiskLevel.cs contains 4-value enum (High, Medium, Low, ReadOnly). PermissionLevelMapping.cs has GetMapping, GetMappings, GetHighestRisk, and GetSimplifiedLabels. All standard SP roles mapped. Unknown roles fallback to Medium. Project compiles.</done>
</task>

<task type="auto">
  <name>Task 2: Create SimplifiedPermissionEntry wrapper and PermissionSummary model</name>
  <files>SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs, SharepointToolbox/Core/Models/PermissionSummary.cs</files>
  <action>
    Create `SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs`:

    ```csharp
    using SharepointToolbox.Core.Helpers;

    namespace SharepointToolbox.Core.Models;

    /// <summary>
    /// Presentation wrapper around PermissionEntry that adds simplified labels
    /// and risk level classification without modifying the immutable source record.
    /// Used as the DataGrid ItemsSource when simplified mode is active.
    /// </summary>
    public class SimplifiedPermissionEntry
    {
        /// <summary>The original immutable PermissionEntry.</summary>
        public PermissionEntry Inner { get; }

        /// <summary>
        /// Human-readable labels for the permission levels.
        /// E.g. "Can edit files and list items" instead of "Contribute".
        /// </summary>
        public string SimplifiedLabels { get; }

        /// <summary>
        /// The highest risk level across all permission levels on this entry.
        /// Used for row-level color coding.
        /// </summary>
        public RiskLevel RiskLevel { get; }

        /// <summary>
        /// Individual mapping results for each permission level in the entry.
        /// Used when detailed breakdown per-role is needed.
        /// </summary>
        public IReadOnlyList<PermissionLevelMapping.MappingResult> Mappings { get; }

        // ── Passthrough properties for DataGrid binding ──

        public string ObjectType => Inner.ObjectType;
        public string Title => Inner.Title;
        public string Url => Inner.Url;
        public bool HasUniquePermissions => Inner.HasUniquePermissions;
        public string Users => Inner.Users;
        public string UserLogins => Inner.UserLogins;
        public string PermissionLevels => Inner.PermissionLevels;
        public string GrantedThrough => Inner.GrantedThrough;
        public string PrincipalType => Inner.PrincipalType;

        public SimplifiedPermissionEntry(PermissionEntry entry)
        {
            Inner = entry;
            Mappings = PermissionLevelMapping.GetMappings(entry.PermissionLevels);
            SimplifiedLabels = PermissionLevelMapping.GetSimplifiedLabels(entry.PermissionLevels);
            RiskLevel = PermissionLevelMapping.GetHighestRisk(entry.PermissionLevels);
        }

        /// <summary>
        /// Creates SimplifiedPermissionEntry wrappers for a collection of entries.
        /// </summary>
        public static IReadOnlyList<SimplifiedPermissionEntry> WrapAll(
            IEnumerable<PermissionEntry> entries)
        {
            return entries.Select(e => new SimplifiedPermissionEntry(e)).ToList();
        }
    }
    ```

    Create `SharepointToolbox/Core/Models/PermissionSummary.cs`:

    ```csharp
    namespace SharepointToolbox.Core.Models;

    /// <summary>
    /// Summary counts of permission entries grouped by risk level.
    /// Displayed in the summary panel when simplified mode is active.
    /// </summary>
    public record PermissionSummary(
        /// <summary>Label for this group (e.g. "High Risk", "Read Only").</summary>
        string Label,
        /// <summary>The risk level this group represents.</summary>
        RiskLevel RiskLevel,
        /// <summary>Number of permission entries at this risk level.</summary>
        int Count,
        /// <summary>Number of distinct users at this risk level.</summary>
        int DistinctUsers
    );

    /// <summary>
    /// Computes PermissionSummary groups from SimplifiedPermissionEntry collections.
    /// </summary>
    public static class PermissionSummaryBuilder
    {
        /// <summary>
        /// Risk level display labels.
        /// </summary>
        private static readonly Dictionary<RiskLevel, string> Labels = new()
        {
            [RiskLevel.High] = "High Risk",
            [RiskLevel.Medium] = "Medium Risk",
            [RiskLevel.Low] = "Low Risk",
            [RiskLevel.ReadOnly] = "Read Only",
        };

        /// <summary>
        /// Builds summary counts grouped by risk level from a collection of simplified entries.
        /// Always returns all 4 risk levels, even if count is 0, for consistent UI binding.
        /// </summary>
        public static IReadOnlyList<PermissionSummary> Build(
            IEnumerable<SimplifiedPermissionEntry> entries)
        {
            var grouped = entries
                .GroupBy(e => e.RiskLevel)
                .ToDictionary(g => g.Key, g => g.ToList());

            return Enum.GetValues<RiskLevel>()
                .Select(level =>
                {
                    var items = grouped.GetValueOrDefault(level, new List<SimplifiedPermissionEntry>());
                    var distinctUsers = items
                        .SelectMany(e => e.UserLogins.Split(';', StringSplitOptions.RemoveEmptyEntries))
                        .Select(u => u.Trim())
                        .Where(u => u.Length > 0)
                        .Distinct(StringComparer.OrdinalIgnoreCase)
                        .Count();

                    return new PermissionSummary(
                        Label: Labels[level],
                        RiskLevel: level,
                        Count: items.Count,
                        DistinctUsers: distinctUsers);
                })
                .ToList();
        }
    }
    ```

    Design notes:
    - SimplifiedPermissionEntry is a class (not record) so it can have passthrough properties for DataGrid binding
    - All original PermissionEntry fields are exposed as passthrough properties — DataGrid columns bind identically
    - SimplifiedLabels and RiskLevel are computed once at construction — no per-render cost
    - PermissionSummaryBuilder.Build always returns 4 entries (one per RiskLevel) for consistent summary panel layout
    - DistinctUsers uses case-insensitive comparison for login deduplication
  </action>
  <verify>
    <automated>cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-incremental 2>&1 | tail -5</automated>
  </verify>
  <done>SimplifiedPermissionEntry wraps PermissionEntry with SimplifiedLabels, RiskLevel, Mappings, and all passthrough properties. PermissionSummary + PermissionSummaryBuilder provide grouped counts. Project compiles cleanly. PermissionEntry.cs is NOT modified.</done>
</task>

</tasks>

<verification>
- `dotnet build SharepointToolbox/SharepointToolbox.csproj` succeeds with 0 errors
- RiskLevel.cs has High, Medium, Low, ReadOnly values
- PermissionLevelMapping has 11 known role mappings with labels and risk levels
- SimplifiedPermissionEntry wraps PermissionEntry (Inner property) without modifying it
- PermissionSummaryBuilder.Build returns 4 summary entries (one per risk level)
- No changes to PermissionEntry.cs
</verification>

<success_criteria>
All 4 files compile cleanly. The mapping and wrapper layer is complete: downstream plans (08-02 through 08-05) can import RiskLevel, PermissionLevelMapping, SimplifiedPermissionEntry, and PermissionSummary without ambiguity. PermissionEntry remains immutable and unmodified.
</success_criteria>

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