modules/ai-guide/Write-AiGuide-Persistence.ps1

param(
    [string]   $ProjectName,
    [string]   $OrmMode,
    [string]   $DbProvider    = "MSSQL",
    [string]   $OutputPath
)

. (Join-Path (Split-Path $MyInvocation.MyCommand.Path) "..\core\Utils.ps1")

$guideDir = Join-Path $OutputPath "ai-guide"
function Write-Guide($fileName, $content) {
    Write-CoreFile (Join-Path $guideDir $fileName) $content
}

$ormShort = switch ($OrmMode) {
    "Dapper" { "Dapper (raw SQL, micro-ORM)" }
    "EFCore" { "EF Core 10 (Code-First, LINQ)" }
    "Hybrid" { "Hybrid - Write: Dapper | Read: EF Core" }
}

$dbNote = switch ($DbProvider) {
    "Postgres" { "PostgreSQL (Npgsql)" }
    default    { "MS SQL Server" }
}

$ormNote = switch ($OrmMode) {
    "Dapper" { "All data access is written using Dapper with raw SQL. EF Core and DbContext do NOT exist." }
    "EFCore" { "All data access is written using EF Core 10. DbContext is used to manage entities Code-First." }
    "Hybrid" { "Write (Add/Update/Delete): Dapper | Read (GetById/GetAll/GetPaged): EF Core with AsNoTracking()" }
}

$dbContextNote = if ($OrmMode -ne "Dapper") { @'
### AppDbContext Registration
When adding a new entity (e.g. `Category`), add its `DbSet` and global query filter (for soft delete) in `AppDbContext.cs`:
```csharp
// {P}-backend/src/{P}.Persistence/Context/AppDbContext.cs
public DbSet<Category> Categories => Set<Category>();
 
// In OnModelCreating:
builder.Entity<Category>().HasQueryFilter(e => !e.IsDeleted);
```
 
### EF Core Value Object Property Conversion
To map a Value Object (like `Email`) to a string database column, configure it in `AppDbContext.cs`:
```csharp
builder.Entity<User>(entity =>
{
    entity.Property(u => u.Email)
        .HasConversion(email => email.Value, value => new Email(value))
        .HasMaxLength(256);
});
```
'@
.Replace("{P}", $ProjectName)
} else { "" }

$dapperNote = if ($OrmMode -match "Dapper|Hybrid") { @'
### Dapper Custom Type Handler
For custom Value Objects (like `Email`), register a custom `SqlMapper.TypeHandler` inside `PersistenceServiceRegistration.cs` so Dapper can map them automatically:
```csharp
// {P}-backend/src/{P}.Persistence/PersistenceServiceRegistration.cs
public class EmailTypeHandler : SqlMapper.TypeHandler<Email>
{
    public override void SetValue(IDbDataParameter parameter, Email? value)
    {
        parameter.Value = value?.Value ?? (object)DBNull.Value;
    }
 
    public override Email? Parse(object value)
    {
        return value is string strVal ? new Email(strVal) : null;
    }
}
 
// In ConfigureServices:
SqlMapper.AddTypeHandler(new EmailTypeHandler());
```
'@
.Replace("{P}", $ProjectName)
} else { "" }

$uowNote = if ($OrmMode -match "EFCore|Hybrid") { @'
### Unit of Work Transaction Management
In EF Core & Hybrid modes, inject `IUnitOfWork` to orchestrate multiple transactions:
```csharp
await _uow.BeginTransactionAsync();
try {
    await _categoryRepo.AddAsync(category);
    await _productRepo.AddAsync(product);
    await _uow.SaveChangesAsync();
    await _uow.CommitTransactionAsync();
} catch {
    await _uow.RollbackTransactionAsync();
    throw;
}
```
'@

} else { "" }

$migrationNote = if ($OrmMode -ne "Dapper") { @'
### EF Core Migrations
Use the path-agnostic scripts inside `{P}-backend/scripts/` to manage migrations:
```powershell
# Windows PowerShell:
cd {P}-backend
.\scripts\migrate.ps1 -MigrationName "AddCategoryTable"
 
# Linux/Mac Bash:
cd {P}-backend
./scripts/migrate.sh AddCategoryTable
```
'@
.Replace("{P}", $ProjectName)
} else { "" }

$persistenceGuideContent = @'
# Persistence Layer AI Developer Guide
 
You are an AI assistant helping developers build the Persistence Layer of the {P} solution.
This layer manages database connections, repository implementations, ORM configuration, and migrations.
 
---
 
## Technical Context
 
- **ORM Strategy:** {O}
- **Database Engine:** {D}
- **Rule:** {R}
 
---
 
## Strict Persistence Rules
 
1. **Clean Interfaces:** Repositories must implement the repository interfaces defined in the Domain layer.
2. **Soft Delete enforcement:** All deletions (`DeleteAsync`) must call `entity.Delete()` domain method to mark the entity as deleted instead of running physical SQL deletes.
3. **Global Filtering:** Always apply global query filters (like `IgnoreQueryFilters()` in EF Core) if soft-deleted items must be explicitly loaded for administrative tasks.
 
---
 
{C}
 
{M}
 
{U}
 
{G}
 
---
 
## Service Registration
 
Every new repository must be registered in the dependency injection container:
```csharp
// {P}-backend/src/{P}.Persistence/PersistenceServiceRegistration.cs
public static class PersistenceServiceRegistration
{
    public static IServiceCollection AddPersistenceServices(this IServiceCollection services, IConfiguration configuration)
    {
        // AppDbContext and connection configuration...
         
        services.AddScoped<ICategoryRepository, CategoryRepository>();
         
        return services;
    }
}
```
'@
.Replace("{P}", $ProjectName).Replace("{O}", $ormShort).Replace("{D}", $dbNote).Replace("{R}", $ormNote).Replace("{C}", $dbContextNote).Replace("{M}", $dapperNote).Replace("{U}", $uowNote).Replace("{G}", $migrationNote)

Write-Guide "persistence-guide.md" $persistenceGuideContent