# Templates DB Seed - Application Skill

> These templates generate EF Core seeds for navigation entities and their translations.

---

## GUID GENERATION RULES

```csharp
// ALWAYS use Guid.NewGuid() for ALL seed data IDs
// This avoids conflicts between different projects/tenants/environments
// Idempotence is handled by Code-based lookups, NOT by fixed IDs

// For HasData() in Configuration files:
Id = Guid.NewGuid()

// For SeedData classes:
public static readonly Guid Sample1Id = Guid.NewGuid();
```

---

---

## TEMPLATE: APPLICATION

### Navigation Entity

```csharp
// In NavigationApplicationConfiguration.cs - HasData()
new {
    Id = Guid.Parse("$APP_GUID"),
    Code = "$CODE",           // e.g.: "sales"
    Label = "$LABEL_EN",      // e.g.: "Sales"
    Description = "$DESC_EN", // e.g.: "Sales management"
    Icon = "$ICON",           // e.g.: "TrendingUp"
    IconType = IconType.Lucide,
    Route = "/$CODE", // e.g.: "/sales"
    DisplayOrder = $ORDER,
    IsActive = true,
    CreatedAt = seedDate
}
```

### Translations (4 languages)

```csharp
// Application translations
translations.Add(new {
    Id = Guid.NewGuid(),
    EntityType = NavigationEntityType.Application,
    EntityId = $APP_GUID,
    LanguageCode = "fr",
    Label = "$LABEL_FR",
    Description = "$DESC_FR",
    CreatedAt = seedDate
});
translations.Add(new {
    Id = Guid.NewGuid(),
    EntityType = NavigationEntityType.Application,
    EntityId = $APP_GUID,
    LanguageCode = "en",
    Label = "$LABEL_EN",
    Description = "$DESC_EN",
    CreatedAt = seedDate
});
translations.Add(new {
    Id = Guid.NewGuid(),
    EntityType = NavigationEntityType.Application,
    EntityId = $APP_GUID,
    LanguageCode = "it",
    Label = "$LABEL_IT",
    Description = "$DESC_IT",
    CreatedAt = seedDate
});
translations.Add(new {
    Id = Guid.NewGuid(),
    EntityType = NavigationEntityType.Application,
    EntityId = $APP_GUID,
    LanguageCode = "de",
    Label = "$LABEL_DE",
    Description = "$DESC_DE",
    CreatedAt = seedDate
});
```

---

## TEMPLATE: MODULE

### Navigation Entity

```csharp
// In NavigationModuleConfiguration.cs - HasData()
new {
    Id = Guid.Parse("$MODULE_GUID"),
    ApplicationId = Guid.Parse("$APP_GUID"),
    Code = "$CODE",                    // e.g.: "products"
    Label = "$LABEL_EN",               // e.g.: "Products"
    Description = "$DESC_EN",          // e.g.: "Product management"
    Icon = "$ICON",                    // e.g.: "Package"
    IconType = IconType.Lucide,
    Route = "/$APP/$CODE",    // e.g.: "/sales/products"
    DisplayOrder = $ORDER,
    IsActive = true,
    CreatedAt = seedDate
}
```

### Translations (4 languages)

```csharp
// Module translations
translations.Add(new {
    Id = Guid.NewGuid(),
    EntityType = NavigationEntityType.Module,
    EntityId = $MODULE_GUID,
    LanguageCode = "fr",
    Label = "$LABEL_FR",
    Description = "$DESC_FR",
    CreatedAt = seedDate
});
translations.Add(new {
    Id = Guid.NewGuid(),
    EntityType = NavigationEntityType.Module,
    EntityId = $MODULE_GUID,
    LanguageCode = "en",
    Label = "$LABEL_EN",
    Description = "$DESC_EN",
    CreatedAt = seedDate
});
translations.Add(new {
    Id = Guid.NewGuid(),
    EntityType = NavigationEntityType.Module,
    EntityId = $MODULE_GUID,
    LanguageCode = "it",
    Label = "$LABEL_IT",
    Description = "$DESC_IT",
    CreatedAt = seedDate
});
translations.Add(new {
    Id = Guid.NewGuid(),
    EntityType = NavigationEntityType.Module,
    EntityId = $MODULE_GUID,
    LanguageCode = "de",
    Label = "$LABEL_DE",
    Description = "$DESC_DE",
    CreatedAt = seedDate
});
```

---

## TEMPLATE: SECTION

### Navigation Entity

```csharp
// In NavigationSectionConfiguration.cs - HasData()
new {
    Id = Guid.Parse("$SECTION_GUID"),
    ModuleId = Guid.Parse("$MODULE_GUID"),
    Code = "$CODE",                           // e.g.: "inventory"
    Label = "$LABEL_EN",                      // e.g.: "Inventory"
    Description = "$DESC_EN",                 // e.g.: "Inventory management"
    Icon = "$ICON",                           // e.g.: "Warehouse"
    IconType = IconType.Lucide,
    Route = "/$APP/$MODULE/$CODE",   // e.g.: "/sales/products/inventory"
    DisplayOrder = $ORDER,
    IsActive = true,
    CreatedAt = seedDate
}
```

### Translations (4 languages)

```csharp
// Section translations
translations.Add(new {
    Id = Guid.NewGuid(),
    EntityType = NavigationEntityType.Section,
    EntityId = $SECTION_GUID,
    LanguageCode = "fr",
    Label = "$LABEL_FR",
    Description = "$DESC_FR",
    CreatedAt = seedDate
});
translations.Add(new {
    Id = Guid.NewGuid(),
    EntityType = NavigationEntityType.Section,
    EntityId = $SECTION_GUID,
    LanguageCode = "en",
    Label = "$LABEL_EN",
    Description = "$DESC_EN",
    CreatedAt = seedDate
});
translations.Add(new {
    Id = Guid.NewGuid(),
    EntityType = NavigationEntityType.Section,
    EntityId = $SECTION_GUID,
    LanguageCode = "it",
    Label = "$LABEL_IT",
    Description = "$DESC_IT",
    CreatedAt = seedDate
});
translations.Add(new {
    Id = Guid.NewGuid(),
    EntityType = NavigationEntityType.Section,
    EntityId = $SECTION_GUID,
    LanguageCode = "de",
    Label = "$LABEL_DE",
    Description = "$DESC_DE",
    CreatedAt = seedDate
});
```

---

## TEMPLATE: PERMISSIONS SEED

### Permission Entity

```csharp
// In PermissionConfiguration.cs - HasData()
// For each CRUD action + assign + execute

// Read
new {
    Id = Guid.Parse("$PERM_READ_GUID"),
    Code = "$APP.$MODULE.read",
    Name = "View $MODULE_LABEL",
    Description = "View $MODULE_LABEL list and details",
    Category = "$APP.$MODULE",
    CreatedAt = seedDate
},
// Create
new {
    Id = Guid.Parse("$PERM_CREATE_GUID"),
    Code = "$APP.$MODULE.create",
    Name = "Create $MODULE_LABEL",
    Description = "Create new $MODULE_LABEL",
    Category = "$APP.$MODULE",
    CreatedAt = seedDate
},
// Update
new {
    Id = Guid.Parse("$PERM_UPDATE_GUID"),
    Code = "$APP.$MODULE.update",
    Name = "Update $MODULE_LABEL",
    Description = "Modify existing $MODULE_LABEL",
    Category = "$APP.$MODULE",
    CreatedAt = seedDate
},
// Delete
new {
    Id = Guid.Parse("$PERM_DELETE_GUID"),
    Code = "$APP.$MODULE.delete",
    Name = "Delete $MODULE_LABEL",
    Description = "Remove $MODULE_LABEL",
    Category = "$APP.$MODULE",
    CreatedAt = seedDate
}
```

### RolePermission Seed (Default Roles)

```csharp
// In RolePermissionConfiguration.cs - HasData()
// SuperAdmin gets all permissions automatically (wildcard)

// PlatformAdmin - if platform context
new { RoleId = Guid.Parse("...PlatformAdminRoleId..."), PermissionId = $PERM_READ_GUID },
new { RoleId = Guid.Parse("...PlatformAdminRoleId..."), PermissionId = $PERM_CREATE_GUID },
new { RoleId = Guid.Parse("...PlatformAdminRoleId..."), PermissionId = $PERM_UPDATE_GUID },
new { RoleId = Guid.Parse("...PlatformAdminRoleId..."), PermissionId = $PERM_DELETE_GUID },

// StandardUser - read only by default
new { RoleId = Guid.Parse("...StandardUserRoleId..."), PermissionId = $PERM_READ_GUID }
```

---

## TEMPLATE: SECTION PERMISSIONS SEED (Level 3)

> **Usage:** When a Module has sub-pages with different permissions

### Permission Entity - Section Level

```csharp
// In PermissionConfiguration.cs - HasData()
// Pattern: {application}.{module}.{section}.{action}

// Declare SectionId (must match NavigationSectionConfiguration.cs)
var {section}SectionId = Guid.Parse("$SECTION_GUID");

// Wildcard Section
new {
    Id = Guid.Parse("$PERM_SECTION_WILDCARD_GUID"),
    Path = "$APP.$MODULE.$SECTION.*",
    Level = PermissionLevel.Section,
    IsWildcard = true,
    SectionId = {section}SectionId,
    Description = "Full $SECTION_LABEL access",
    CreatedAt = seedDate
},
// Read
new {
    Id = Guid.Parse("$PERM_SECTION_READ_GUID"),
    Path = "$APP.$MODULE.$SECTION.read",
    Level = PermissionLevel.Section,
    Action = PermissionAction.Read,
    IsWildcard = false,
    SectionId = {section}SectionId,
    Description = "View $SECTION_LABEL",
    CreatedAt = seedDate
},
// Create
new {
    Id = Guid.Parse("$PERM_SECTION_CREATE_GUID"),
    Path = "$APP.$MODULE.$SECTION.create",
    Level = PermissionLevel.Section,
    Action = PermissionAction.Create,
    IsWildcard = false,
    SectionId = {section}SectionId,
    Description = "Create in $SECTION_LABEL",
    CreatedAt = seedDate
},
// Update
new {
    Id = Guid.Parse("$PERM_SECTION_UPDATE_GUID"),
    Path = "$APP.$MODULE.$SECTION.update",
    Level = PermissionLevel.Section,
    Action = PermissionAction.Update,
    IsWildcard = false,
    SectionId = {section}SectionId,
    Description = "Update in $SECTION_LABEL",
    CreatedAt = seedDate
},
// Delete
new {
    Id = Guid.Parse("$PERM_SECTION_DELETE_GUID"),
    Path = "$APP.$MODULE.$SECTION.delete",
    Level = PermissionLevel.Section,
    Action = PermissionAction.Delete,
    IsWildcard = false,
    SectionId = {section}SectionId,
    Description = "Delete in $SECTION_LABEL",
    CreatedAt = seedDate
},
// Execute (optional)
new {
    Id = Guid.Parse("$PERM_SECTION_EXECUTE_GUID"),
    Path = "$APP.$MODULE.$SECTION.execute",
    Level = PermissionLevel.Section,
    Action = PermissionAction.Execute,
    IsWildcard = false,
    SectionId = {section}SectionId,
    Description = "Execute actions in $SECTION_LABEL",
    CreatedAt = seedDate
}
```

---

## TEMPLATE: RESOURCE PERMISSIONS SEED (Level 4)

> **Usage:** Finest level - sub-resources with distinct permissions (e.g.: Prompts → Blocks)

### Permission Entity - Resource Level

```csharp
// In PermissionConfiguration.cs - HasData()
// Pattern: {application}.{module}.{section}.{resource}.{action}

// Declare ResourceId (must match NavigationResourceConfiguration.cs)
var {resource}ResourceId = Guid.Parse("$RESOURCE_GUID");

// Wildcard Resource
new {
    Id = Guid.Parse("$PERM_RESOURCE_WILDCARD_GUID"),
    Path = "$APP.$MODULE.$SECTION.$RESOURCE.*",
    Level = PermissionLevel.Resource,
    IsWildcard = true,
    ResourceId = {resource}ResourceId,
    Description = "Full $RESOURCE_LABEL access",
    CreatedAt = seedDate
},
// Read
new {
    Id = Guid.Parse("$PERM_RESOURCE_READ_GUID"),
    Path = "$APP.$MODULE.$SECTION.$RESOURCE.read",
    Level = PermissionLevel.Resource,
    Action = PermissionAction.Read,
    IsWildcard = false,
    ResourceId = {resource}ResourceId,
    Description = "View $RESOURCE_LABEL",
    CreatedAt = seedDate
},
// Create
new {
    Id = Guid.Parse("$PERM_RESOURCE_CREATE_GUID"),
    Path = "$APP.$MODULE.$SECTION.$RESOURCE.create",
    Level = PermissionLevel.Resource,
    Action = PermissionAction.Create,
    IsWildcard = false,
    ResourceId = {resource}ResourceId,
    Description = "Create $RESOURCE_LABEL",
    CreatedAt = seedDate
},
// Update
new {
    Id = Guid.Parse("$PERM_RESOURCE_UPDATE_GUID"),
    Path = "$APP.$MODULE.$SECTION.$RESOURCE.update",
    Level = PermissionLevel.Resource,
    Action = PermissionAction.Update,
    IsWildcard = false,
    ResourceId = {resource}ResourceId,
    Description = "Update $RESOURCE_LABEL",
    CreatedAt = seedDate
},
// Delete
new {
    Id = Guid.Parse("$PERM_RESOURCE_DELETE_GUID"),
    Path = "$APP.$MODULE.$SECTION.$RESOURCE.delete",
    Level = PermissionLevel.Resource,
    Action = PermissionAction.Delete,
    IsWildcard = false,
    ResourceId = {resource}ResourceId,
    Description = "Delete $RESOURCE_LABEL",
    CreatedAt = seedDate
}
```

---

## TEMPLATE: BULK OPERATIONS PERMISSIONS

> **MANDATORY:** Always provide for CRUD modules

### Permission Entity - Bulk Operations

```csharp
// In PermissionConfiguration.cs - HasData()
// Add after standard CRUD permissions

// Bulk Create
new {
    Id = Guid.Parse("$PERM_BULK_CREATE_GUID"),
    Path = "$APP.$MODULE.bulk-create",
    Level = PermissionLevel.Module,
    Action = PermissionAction.Create,
    IsWildcard = false,
    ModuleId = {module}ModuleId,
    Description = "Bulk create $MODULE_LABEL",
    CreatedAt = seedDate
},
// Bulk Update
new {
    Id = Guid.Parse("$PERM_BULK_UPDATE_GUID"),
    Path = "$APP.$MODULE.bulk-update",
    Level = PermissionLevel.Module,
    Action = PermissionAction.Update,
    IsWildcard = false,
    ModuleId = {module}ModuleId,
    Description = "Bulk update $MODULE_LABEL",
    CreatedAt = seedDate
},
// Bulk Delete
new {
    Id = Guid.Parse("$PERM_BULK_DELETE_GUID"),
    Path = "$APP.$MODULE.bulk-delete",
    Level = PermissionLevel.Module,
    Action = PermissionAction.Delete,
    IsWildcard = false,
    ModuleId = {module}ModuleId,
    Description = "Bulk delete $MODULE_LABEL",
    CreatedAt = seedDate
},
// Export
new {
    Id = Guid.Parse("$PERM_EXPORT_GUID"),
    Path = "$APP.$MODULE.export",
    Level = PermissionLevel.Module,
    Action = PermissionAction.Execute,
    IsWildcard = false,
    ModuleId = {module}ModuleId,
    Description = "Export $MODULE_LABEL data",
    CreatedAt = seedDate
},
// Import
new {
    Id = Guid.Parse("$PERM_IMPORT_GUID"),
    Path = "$APP.$MODULE.import",
    Level = PermissionLevel.Module,
    Action = PermissionAction.Create,
    IsWildcard = false,
    ModuleId = {module}ModuleId,
    Description = "Import $MODULE_LABEL data",
    CreatedAt = seedDate
}
```

---

## COMPLETE EXAMPLE: "Products" Module in "Sales"

### Variables

```
$APP = sales
$APP_GUID = (generated via Guid.NewGuid() at seed time)
$MODULE = products
$MODULE_GUID = (generated via Guid.NewGuid() at seed time)

$LABEL_FR = Produits
$LABEL_EN = Products
$LABEL_IT = Prodotti
$LABEL_DE = Produkte

$DESC_FR = Gestion des produits
$DESC_EN = Product management
$DESC_IT = Gestione prodotti
$DESC_DE = Produktverwaltung

$ICON = Package
$ORDER = 1
```

### Files to modify

1. `NavigationApplicationConfiguration.cs` - Add Sales application
2. `NavigationModuleConfiguration.cs` - Add Products module
3. `NavigationTranslationConfiguration.cs` - Add 4 translations × number of entities
4. `PermissionConfiguration.cs` - Add CRUD permissions
5. `RolePermissionConfiguration.cs` - Assign to default roles

---

## TEMPLATE: ENTITY SEED DATA (SeedData Provider)

> **Usage:** Runtime seed data for domain entities (Products, Orders, Tickets, etc.)
> **When:** User wants initial data for development/testing
> **Mechanism:** Loaded at startup by DevDataSeeder, NOT via EF Core HasData/migrations
> **Architecture:** Identical in core AND extensions

### Two Seeding Mechanisms

| Mechanism | When to Use | Applied Via |
|-----------|-------------|-------------|
| **HasData()** in Configuration.cs | System entities (Navigation, Permissions, Roles) | EF Core migrations |
| **SeedData.cs + DevDataSeeder** | Domain entities (Tickets, Products, Orders) | Application startup |

**Rule:** Navigation, Permission, and RolePermission seeds use HasData().
Domain entity seeds use the SeedData provider pattern below.

### SeedData Class Pattern

```csharp
// Infrastructure/Persistence/Seeding/Data/{Domain}/{EntityName}SeedData.cs

using SmartStack.Domain.{Application}.{Module};

namespace SmartStack.Infrastructure.Persistence.Seeding.Data.{Domain};

/// <summary>
/// Demo {EntityName} data for development and testing.
/// </summary>
public static class {EntityName}SeedData
{
    // ============================================================
    // RANDOM IDs - generated at class load, unique per deployment
    // Idempotence is handled by AnyAsync() checks, NOT by fixed IDs
    // ============================================================

    public static readonly Guid Sample1Id = Guid.NewGuid();
    public static readonly Guid Sample2Id = Guid.NewGuid();
    public static readonly Guid Sample3Id = Guid.NewGuid();

    /// <summary>
    /// Returns all demo {EntityName} entities.
    /// </summary>
    internal static IEnumerable<{EntityName}SeedItem> GetAll{EntityName}s()
    {
        return new[]
        {
            new {EntityName}SeedItem
            {
                Id = Sample1Id,
                // ... entity properties with realistic sample data
                CreatedByUserId = UserSeedData.SystemUserId
            },
            new {EntityName}SeedItem
            {
                Id = Sample2Id,
                // ... varied data
                CreatedByUserId = UserSeedData.SystemUserId
            },
            new {EntityName}SeedItem
            {
                Id = Sample3Id,
                // ... varied data
                CreatedByUserId = UserSeedData.SystemUserId
            }
        };
    }
}

/// <summary>
/// Seed item DTO for {EntityName} (avoids domain factory methods during seeding).
/// </summary>
internal class {EntityName}SeedItem
{
    public Guid Id { get; init; }
    // ... all required properties matching the domain entity
    public Guid CreatedByUserId { get; init; }
}
```

### Multi-Tenant Entity Seed Pattern

For entities belonging to a tenant, organize by tenant:

```csharp
internal static IEnumerable<{EntityName}SeedItem> GetAll{EntityName}s()
{
    var items = new List<{EntityName}SeedItem>();

    items.AddRange(GetTenant1{EntityName}s());
    items.AddRange(GetTenant2{EntityName}s());

    return items;
}

private static IEnumerable<{EntityName}SeedItem> GetTenant1{EntityName}s()
{
    var tenantId = TenantSeedData.AcmeCorporationId;
    var userId = TenantMembershipSeedData.AcmeOwnerId;

    return new[]
    {
        new {EntityName}SeedItem
        {
            Id = Guid.NewGuid(),
            TenantId = tenantId,
            CreatedByUserId = userId,
            // ... properties
        }
    };
}
```

### DevDataSeeder Registration Pattern

```csharp
// In Infrastructure/Persistence/Seeding/DevDataSeeder.cs

// 1. Add using statement
using SmartStack.Infrastructure.Persistence.Seeding.Data.{Domain};

// 2. Add call in SeedAsync() method (after existing seed calls)
public async Task SeedAsync(CancellationToken cancellationToken = default)
{
    // ... existing seed calls ...

    await Seed{EntityName}sAsync(cancellationToken);  // <-- ADD THIS

    // ... summary logging ...
}

// 3. Add private seeding method
private async Task Seed{EntityName}sAsync(CancellationToken cancellationToken)
{
    _logger.LogInformation("Seeding demo {EntityName}s...");

    var existingCount = await _context.{EntityName}s.CountAsync(cancellationToken);
    if (existingCount > 0)
    {
        _logger.LogWarning("{EntityName}s already seeded ({Count} exist), skipping.", existingCount);
        return;
    }

    var createdCount = 0;

    foreach (var seedItem in {EntityName}SeedData.GetAll{EntityName}s())
    {
        var entity = {EntityName}.Create(
            // Map seedItem properties to factory method parameters
        );

        // Factory method already generates a random GUID via Guid.NewGuid()
        // No need to override — each seed run creates fresh unique IDs

        _context.{EntityName}s.Add(entity);
        createdCount++;
    }

    if (createdCount > 0)
    {
        await _context.SaveChangesAsync(cancellationToken);
    }

    _logger.LogInformation("Created {Count} demo {EntityName}s.", createdCount);
}
```

### Seed Data GUID Generation

```csharp
// ALWAYS use Guid.NewGuid() — avoids conflicts between projects/tenants/environments
public static readonly Guid ProductAlphaId = Guid.NewGuid();
public static readonly Guid ProductBetaId  = Guid.NewGuid();

// FORBIDDEN: Deterministic GUIDs (SHA256, sequential, hardcoded)
// These create conflicts when multiple projects seed into the same database
```

### Best Practices

| Practice | Description |
|----------|-------------|
| Random IDs | ALWAYS use `Guid.NewGuid()` — avoids cross-project/tenant conflicts |
| Idempotent | Always check `if exists` before creating (by Code, not by ID) |
| Use factory methods | Call `Entity.Create(...)` not `new Entity()` |
| FK by Code lookup | Resolve foreign keys by Code at runtime, not by hardcoded GUID |
| Realistic data | Use plausible names, descriptions, amounts |
| Cover all states | Include entities in different statuses (active, closed, etc.) |
| Reference existing seeds | Resolve FKs via Code/Name lookups, not static GUIDs |
| SaveChanges per batch | Call SaveChanges after each entity group |

---

## TEMPLATE: CLIENT SEED DATA PROVIDER

> **Usage:** Client projects (projectType: "client") to seed data into the Core schema
> **Mechanism:** Runtime seeding via IClientSeedDataProvider (no Core migrations required)
> **When:** Generated automatically at step 03b if the project is of type client

### Difference with HasData()

| Aspect | HasData() (core) | IClientSeedDataProvider (client) |
|--------|-----------------|----------------------------------|
| When | EF Core migration | Application startup |
| Where | In Core migrations | Runtime via CoreDbContext |
| Who | SmartStack.app | Client project |
| Idempotent | Yes (single migration) | Yes (check existence) |
| Core migration | Yes | No |
| Entities created with | Anonymous objects | Factory methods (mandatory) |

### Implementation Pattern

The provider is generated in:
`Infrastructure/Persistence/Seeding/{AppPascalName}SeedDataProvider.cs`

It consumes the SeedData files generated in steps 01-03:
- `{Module}NavigationSeedData.cs` (GUIDs + navigation data)
- `{Module}NavigationTranslationSeedData.cs` (4-language translations)
- `{Module}PermissionSeedData.cs` (RBAC permissions)
- `{Module}RolePermissionSeedData.cs` (role -> permission mappings)

### DI Registration

In `Infrastructure/DependencyInjection.cs`:
```csharp
services.AddScoped<IClientSeedDataProvider, {AppPascalName}SeedDataProvider>();
```

### Execution Pipeline

```
InitializeSmartStackAsync()
  1. MigrateAsync()                   -> Core schema created
  2. IDatabaseSeeder.SeedAsync()      -> System users, tenant
  3. IClientSeedDataProvider.Seed*()  -> Navigation + Roles + Permissions + RolePermissions CLIENT
  4. IDevDataSeeder.SeedAsync()       -> Dev data
  5. InitializeNavigationRoutingAsync -> Routes loaded (including client routes)
```

### Provider Template

```csharp
using Microsoft.EntityFrameworkCore;
using SmartStack.Application.Common.Interfaces;
using SmartStack.Domain.Navigation;
using SmartStack.Domain.Administration.Roles;

namespace {BaseNamespace}.Infrastructure.Persistence.Seeding;

public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
{
    public int Order => 100;

    public async Task SeedNavigationAsync(ICoreDbContext context, CancellationToken ct)
    {
        // NOTE: Idempotence is at MODULE level (not application level).
        // If the application already exists, we reuse it and seed any missing modules.
        // This allows adding Module 2+ to an existing application.
        var existingApp = await context.NavigationApplications
            .FirstOrDefaultAsync(a => a.Code == "{app_code}", ct);

        NavigationApplication app;
        if (existingApp != null)
        {
            app = existingApp; // Application already seeded — reuse for module seeding below
        }
        else
        {
            app = NavigationApplication.Create(
                ApplicationZone.Business, "{app_code}", "{app_label_en}",
                "{app_desc_en}", "{app_icon}", IconType.Lucide,
                "/{app_code}", {display_order});
            context.NavigationApplications.Add(app);
            await ((DbContext)context).SaveChangesAsync(ct);

            // Application translations (only when creating new application)
            // foreach (var t in NavigationApplicationSeedData.GetTranslationEntries()) { ... }
            await ((DbContext)context).SaveChangesAsync(ct);
        }

        // Create modules (idempotent per-module — check each before inserting)
        // var mod1Exists = await context.NavigationModules.AnyAsync(m => m.Code == "{module_code}" && m.ApplicationId == app.Id, ct);
        // if (!mod1Exists) { var mod1 = NavigationModule.Create(...); context.NavigationModules.Add(mod1); }

        // Module translations — IDEMPOTENT (unique index IX_nav_Translations_EntityType_EntityId_LanguageCode)
        // CRITICAL: Always check before inserting to avoid duplicate key errors on re-runs
        // CRITICAL: Use modEntity.Id (actual DB ID), NOT seed-time GUID from SeedData class.
        // if (!await context.NavigationTranslations.AnyAsync(
        //     t => t.EntityId == mod1Entity.Id && t.EntityType == NavigationEntityType.Module, ct))
        // { foreach (var t in {Module}NavigationSeedData.GetTranslationEntries())
        //   { context.NavigationTranslations.Add(
        //       NavigationTranslation.Create(t.EntityType, mod1Entity.Id, t.LanguageCode, t.Label, t.Description)); } }

        // Sections (idempotent per-section — check each before inserting)
        // var secExists = await context.NavigationSections.AnyAsync(s => s.Code == secEntry.Code && s.ModuleId == ..., ct);
        // Use NavigationSection.Create(moduleId, code, label, description, icon, iconType, route, displayOrder)

        // Section translations — IDEMPOTENT (same guard pattern as module translations)
        // CRITICAL: Resolve actual section from DB (secEntry.Id is seed-time GUID ≠ DB ID):
        // var actualSection = await context.NavigationSections
        //     .FirstAsync(s => s.Code == secEntry.Code && s.ModuleId == modEntity.Id, ct);
        // if (!await context.NavigationTranslations.AnyAsync(
        //     t => t.EntityId == actualSection.Id && t.EntityType == NavigationEntityType.Section, ct))

        // Resources (idempotent per-resource — check each before inserting)
        // var resExists = await context.NavigationResources.AnyAsync(r => r.Code == resEntry.Code && r.SectionId == ..., ct);
        // Use NavigationResource.Create(sectionId, code, label, entityType, route, displayOrder)
    }

    public async Task SeedRolesAsync(ICoreDbContext context, CancellationToken ct)
    {
        // Resolve application from DB by Code — NOT seed-time GUID
        var app = await context.NavigationApplications
            .FirstAsync(a => a.Code == "{app_code}", ct);

        // Check idempotence — scoped by actual DB ApplicationId
        var exists = await context.Roles
            .AnyAsync(r => r.ApplicationId == app.Id, ct);
        if (exists) return;

        // Create application-scoped roles (Admin, Manager, Contributor, Viewer)
        foreach (var entry in ApplicationRolesSeedData.GetRoleEntries())
        {
            var role = Role.Create(
                name: entry.Name,
                shortName: entry.Code,
                category: RoleCategory.Application,
                description: entry.Description,
                isSystem: entry.IsSystem,
                applicationId: app.Id,
                code: entry.Code);
            context.Roles.Add(role);
        }
        await ((DbContext)context).SaveChangesAsync(ct);
    }

    public async Task SeedPermissionsAsync(ICoreDbContext context, CancellationToken ct)
    {
        var exists = await context.Permissions
            .AnyAsync(p => p.Path == "{full_path}.*", ct);
        if (exists) return;

        // Create permissions from {Module}PermissionSeedData
        // Use Permission.CreateForModule(...) and Permission.CreateWildcard(...)
    }

    public async Task SeedRolePermissionsAsync(ICoreDbContext context, CancellationToken ct)
    {
        var exists = await context.RolePermissions
            .AnyAsync(rp => rp.Permission!.Path.StartsWith("{full_path}."), ct);
        if (exists) return;

        // Create role-permissions from {Module}RolePermissionSeedData
        // Use RolePermission.Create(roleId, permissionId, "system")
    }
}
```

### Critical Rules

| Rule | Description |
|------|-------------|
| Factory methods | `NavigationModule.Create(...)`, `NavigationSection.Create(...)`, `NavigationResource.Create(...)`, `Permission.CreateForModule(...)` - NEVER `new Entity()` |
| Idempotence | Each Seed method checks existence before inserting |
| SaveChanges per group | Navigation -> save -> Roles -> save -> Permissions -> save -> RolePermissions -> save |
| Random GUIDs | ALWAYS use `Guid.NewGuid()` — resolve FKs by Code lookup |
| FK resolution by Code | Parent modules found by `Code`, not hardcoded GUID |
| Section/Resource conditionality | Only generate if `seedDataCore.navigationSections` / `seedDataCore.navigationResources` exist in feature.json |

---

## SEED CHECKLIST

| Check | Status |
|-------|--------|
| ☐ Random GUIDs via Guid.NewGuid() (no deterministic/sequential/fixed) | |
| ☐ 4 languages for each navigation entity (modules, sections, resources) | |
| ☐ Translation IDs use Guid.NewGuid() | |
| ☐ Route aligned with permission path | |
| ☐ DisplayOrder consistent | |
| ☐ CRUD permissions created (Level 2 - Module) | |
| ☐ Section permissions if sub-pages (Level 3) | |
| ☐ NavigationSections seeded (if `seedDataCore.navigationSections` in feature.json) | |
| ☐ NavigationResources seeded (if `seedDataCore.navigationResources` in feature.json) | |
| ☐ Resource permissions if sub-resources (Level 4) | |
| ☐ Bulk Operations permissions created | |
| ☐ RolePermissions assigned | |
| ☐ Entity SeedData.cs created (if user opted in) | |
| ☐ DevDataSeeder.cs updated (if user opted in) | |
| ☐ IClientSeedDataProvider generated (client projects only) | |
| ☐ Provider registered in DI (client projects only) | |

---

## PERMISSIONS HIERARCHY (4 Levels)

```
Level 1: APPLICATION
└─ Path: {application}.*
   └─ E.g.: administration.* → Full app access

Level 2: MODULE
└─ Path: {application}.{module}.{action}
   └─ E.g.: administration.users.read

Level 3: SECTION
└─ Path: {application}.{module}.{section}.{action}
   └─ E.g.: administration.ai.settings.update

Level 4: RESOURCE (finest granularity)
└─ Path: {application}.{module}.{section}.{resource}.{action}
   └─ E.g.: administration.ai.prompts.blocks.delete
```

---

## SQL OBJECTS INFRASTRUCTURE

> **Usage:** SQL Server functions (TVFs, scalar), views, and stored procedures managed as embedded resources
> **Pattern:** Same as SmartStack.app — `CREATE OR ALTER` for idempotency, applied automatically at startup
> **Location:** `Infrastructure/Persistence/SqlObjects/`

### Directory Structure

```
Infrastructure/Persistence/SqlObjects/
├── SqlObjectHelper.cs              ← Helper to apply SQL from embedded resources
├── Functions/                       ← Table-Valued & Scalar functions
│   └── fn_{FunctionName}.sql       ← CREATE OR ALTER FUNCTION [schema].[fn_Name]
├── Views/                           ← Future: SQL views
│   └── vw_{ViewName}.sql
└── StoredProcedures/                ← Future: Stored procedures
    └── sp_{ProcName}.sql
```

### SqlObjectHelper Pattern

```csharp
// Infrastructure/Persistence/SqlObjects/SqlObjectHelper.cs

using System.Reflection;
using Microsoft.EntityFrameworkCore.Migrations;

namespace {BaseNamespace}.Infrastructure.Persistence.SqlObjects;

/// <summary>
/// Helper for applying SQL objects from embedded resources.
/// SQL files use CREATE OR ALTER for idempotency — applied automatically at startup.
/// </summary>
public static class SqlObjectHelper
{
    private static readonly Assembly Assembly = typeof(SqlObjectHelper).Assembly;
    private const string ResourcePrefix = "{BaseNamespace}.Infrastructure.Persistence.SqlObjects.";

    /// <summary>
    /// Applies all SQL objects directly to the database via DbContext.
    /// Called at startup after MigrateAsync() — ensures SQL objects are always up-to-date
    /// without requiring a new migration when a .sql file changes.
    /// </summary>
    public static async Task ApplyAllAsync(DbContext dbContext)
    {
        await ApplyCategoryToDbAsync(dbContext, "Functions");
        // Future: await ApplyCategoryToDbAsync(dbContext, "Views");
        // Future: await ApplyCategoryToDbAsync(dbContext, "StoredProcedures");
    }

    /// <summary>
    /// Applies all SQL objects via MigrationBuilder (legacy, kept for backward compatibility).
    /// </summary>
    public static void ApplyAll(MigrationBuilder migrationBuilder)
    {
        ApplyAllFunctions(migrationBuilder);
    }

    public static void ApplyAllFunctions(MigrationBuilder migrationBuilder)
    {
        ApplyCategory(migrationBuilder, "Functions");
    }

    public static string ReadSqlObject(string resourceName)
    {
        var fullName = ResourcePrefix + resourceName;
        using var stream = Assembly.GetManifestResourceStream(fullName)
            ?? throw new InvalidOperationException(
                $"SQL object resource '{fullName}' not found. " +
                $"Ensure the .sql file is marked as EmbeddedResource in the .csproj. " +
                $"Available: {string.Join(", ", Assembly.GetManifestResourceNames().Where(n => n.EndsWith(".sql")))}");
        using var reader = new StreamReader(stream);
        return reader.ReadToEnd();
    }

    private static void ApplyCategory(MigrationBuilder migrationBuilder, string category)
    {
        var prefix = ResourcePrefix + category + ".";
        var resources = Assembly.GetManifestResourceNames()
            .Where(n => n.StartsWith(prefix, StringComparison.Ordinal)
                     && n.EndsWith(".sql", StringComparison.Ordinal))
            .OrderBy(n => n, StringComparer.Ordinal);

        foreach (var resource in resources)
        {
            using var stream = Assembly.GetManifestResourceStream(resource)!;
            using var reader = new StreamReader(stream);
            migrationBuilder.Sql(reader.ReadToEnd());
        }
    }

    private static async Task ApplyCategoryToDbAsync(DbContext dbContext, string category)
    {
        var prefix = ResourcePrefix + category + ".";
        var resources = Assembly.GetManifestResourceNames()
            .Where(n => n.StartsWith(prefix, StringComparison.Ordinal)
                     && n.EndsWith(".sql", StringComparison.Ordinal))
            .OrderBy(n => n, StringComparer.Ordinal);

        foreach (var resource in resources)
        {
            using var stream = Assembly.GetManifestResourceStream(resource)!;
            using var reader = new StreamReader(stream);
            await dbContext.Database.ExecuteSqlRawAsync(reader.ReadToEnd());
        }
    }
}
```

### .csproj Configuration (REQUIRED)

```xml
<ItemGroup>
  <EmbeddedResource Include="Persistence\SqlObjects\**\*.sql" />
</ItemGroup>
```

### SQL File Convention

All `.sql` files MUST use `CREATE OR ALTER` for idempotency:

```sql
-- Infrastructure/Persistence/SqlObjects/Functions/fn_GetUserGroupHierarchy.sql
CREATE OR ALTER FUNCTION [extensions].[fn_GetEntityHierarchy](@EntityId UNIQUEIDENTIFIER)
RETURNS TABLE
AS
RETURN
(
    WITH Hierarchy AS (
        SELECT Id, ParentId, 0 AS Level
        FROM [extensions].[fb_Entities]
        WHERE Id = @EntityId
        UNION ALL
        SELECT child.Id, child.ParentId, parent.Level + 1
        FROM [extensions].[fb_Entities] child
        INNER JOIN Hierarchy parent ON child.ParentId = parent.Id
        WHERE parent.Level < 10
    )
    SELECT DISTINCT Id FROM Hierarchy
);
```

### Startup Integration

SQL objects are applied **automatically at application startup** via `SqlObjectHelper.ApplyAllAsync(dbContext)`, called in `SmartStackExtensions.InitializeSmartStackAsync()` right after `MigrateAsync()`. No migration injection needed — just add/modify `.sql` files and restart the application.

### When to Use SQL Objects

| Need | Solution |
|------|----------|
| Recursive hierarchy traversal | TVF with CTE (fn_GetXxxHierarchy) |
| Complex aggregation for reports | SQL View (vw_XxxSummary) |
| Bulk operations with business logic | Stored Procedure (sp_XxxBatch) |
| Simple CRUD, filtering, sorting | EF Core LINQ (no SQL needed) |
