# Padrões de Código - MORPH Framework

## 📝 Nomenclatura

### Classes e Interfaces
```csharp
// Interfaces: sempre com prefixo I
public interface IReportService { }
public interface IReportRepository { }

// Classes: PascalCase, sufixo indica tipo
public class ReportService : IReportService { }
public class ReportRepository : IReportRepository { }
public class ReportController : ControllerBase { }
public class ReportGeneratorJob { }  // Hangfire job
public class ReportAnalyzerAgent { } // AI Agent
```

### Métodos
```csharp
// Async sempre com sufixo Async
public async Task<Report> GetByIdAsync(int id) { }
public async Task CreateAsync(Report report) { }

// Métodos síncronos sem sufixo
public Report GetById(int id) { }
public void Validate(Report report) { }
```

### Variáveis e Parâmetros
```csharp
// camelCase para variáveis locais e parâmetros
public async Task ProcessAsync(string reportId, CancellationToken cancellationToken)
{
    var report = await _repository.GetByIdAsync(reportId);
    var analysisResult = await _analyzer.AnalyzeAsync(report);
}

// _camelCase para campos privados
private readonly IReportRepository _repository;
private readonly ILogger<ReportService> _logger;
```

---

## 📁 Estrutura de Projeto

```
src/
├── MyProject.Web/              # Blazor Server
│   ├── Components/
│   │   ├── Pages/              # Páginas (rotas)
│   │   ├── Shared/             # Componentes compartilhados
│   │   └── Layout/             # Layouts
│   └── Program.cs
│
├── MyProject.Application/      # Casos de uso
│   ├── Features/
│   │   └── Reports/
│   │       ├── Commands/
│   │       ├── Queries/
│   │       └── Services/
│   └── Common/
│
├── MyProject.Domain/           # Entidades e regras
│   ├── Entities/
│   ├── ValueObjects/
│   └── Enums/
│
├── MyProject.Infrastructure/   # Implementações
│   ├── Data/
│   │   ├── Configurations/
│   │   ├── Migrations/
│   │   └── AppDbContext.cs
│   └── Services/
│
└── MyProject.Agents/           # MS Agent Framework
    └── ReportAnalyzer/
```

---

## 💉 Dependency Injection

```csharp
// Usar primary constructor (C# 12+)
public class ReportService(
    IReportRepository repository,
    ILogger<ReportService> logger) : IReportService
{
    public async Task<Report> GetByIdAsync(int id)
    {
        logger.LogInformation("Getting report {ReportId}", id);
        return await repository.GetByIdAsync(id);
    }
}
```

---

## 🗄️ Entity Framework Core

### Entidades
```csharp
public class ReportSchedule
{
    public int Id { get; private set; }
    public string Name { get; private set; } = string.Empty;
    public ReportType ReportType { get; private set; }
    public string CronExpression { get; private set; } = string.Empty;
    public List<string> Recipients { get; private set; } = [];
    public bool IsActive { get; private set; }
    public DateTime CreatedAt { get; private set; }

    // Factory method para criação
    public static ReportSchedule Create(string name, ReportType reportType,
        string cronExpression, List<string> recipients)
    {
        return new ReportSchedule
        {
            Name = name,
            ReportType = reportType,
            CronExpression = cronExpression,
            Recipients = recipients,
            IsActive = true,
            CreatedAt = DateTime.UtcNow
        };
    }

    // Métodos de domínio
    public void Activate() => IsActive = true;
    public void Deactivate() => IsActive = false;
}
```

### Configuração Fluent API
```csharp
public class ReportScheduleConfiguration : IEntityTypeConfiguration<ReportSchedule>
{
    public void Configure(EntityTypeBuilder<ReportSchedule> builder)
    {
        builder.ToTable("ReportSchedules");
        builder.HasKey(x => x.Id);

        builder.Property(x => x.Name)
            .IsRequired()
            .HasMaxLength(200);

        // JSON column para lista
        builder.Property(x => x.Recipients)
            .HasConversion(
                v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
                v => JsonSerializer.Deserialize<List<string>>(v, (JsonSerializerOptions?)null) ?? [])
            .HasColumnType("nvarchar(max)");
    }
}
```

---

## 🔥 Componentes Blazor

```razor
@page "/reports/schedules"
@attribute [Authorize(Policy = "CanManageReports")]
@inject IReportScheduleService ReportService
@inject ILogger<ReportScheduleList> Logger

<PageTitle>Report Schedules</PageTitle>

@if (_isLoading)
{
    <LoadingSpinner />
}
else if (_schedules is null || !_schedules.Any())
{
    <EmptyState Message="No schedules configured" />
}
else
{
    <div class="schedule-list">
        @foreach (var schedule in _schedules)
        {
            <ScheduleCard Schedule="schedule" OnToggle="HandleToggleAsync" />
        }
    </div>
}

@code {
    private List<ReportScheduleDto>? _schedules;
    private bool _isLoading = true;

    protected override async Task OnInitializedAsync()
    {
        try
        {
            _schedules = await ReportService.GetAllAsync();
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, "Failed to load schedules");
        }
        finally
        {
            _isLoading = false;
        }
    }
}
```

### Padrões Obrigatórios
- ✅ Loading state em toda página
- ✅ Empty state quando lista vazia
- ✅ Error handling com try/catch
- ✅ `@inject` para DI (não construtor)
- ✅ `@attribute [Authorize]` quando necessário

---

## 🤖 AI Agents - Padrão Obrigatório (.NET 10)

> **CRÍTICO:** Use **exclusivamente** Microsoft Agent Framework.
> Semantic Kernel foi descontinuado no MORPH-SPEC.

### Pattern Obrigatório

```csharp
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;

public class ReportAnalyzerAgent(
    IChatClient chatClient,
    ILogger<ReportAnalyzerAgent> logger) : IReportAnalyzerAgent
{
    public async Task<AnalysisResult> AnalyzeAsync(
        ReportData reportData,
        CancellationToken cancellationToken = default)
    {
        var agent = chatClient.CreateAgent(
            instructions: """
                Você é um especialista em análise de relatórios.
                Analise os dados fornecidos e retorne em JSON:
                {
                  "summary": "resumo",
                  "insights": ["top 3 insights"],
                  "recommendations": ["recomendações"]
                }
                """,
            name: "ReportAnalyzer"
        );

        var prompt = $"Analise este relatório:\n{reportData.ToJson()}";

        try
        {
            var response = await agent.RunAsync(prompt, cancellationToken: cancellationToken);
            return ParseResponse(response.Content);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Analysis failed for report {ReportId}", reportData.Id);
            throw new ReportAnalysisException("Failed to analyze", ex);
        }
    }
}
```

### Configuração (Program.cs)

```csharp
// Registrar ChatClient
builder.Services.AddSingleton<IChatClient>(sp =>
{
    var config = sp.GetRequiredService<IConfiguration>();
    return new ChatClient(
        model: "gpt-4o-mini",
        credential: new ApiKeyCredential(config["AzureOpenAI:ApiKey"]!),
        endpoint: new Uri(config["AzureOpenAI:Endpoint"]!)
    );
});

// Registrar agent
builder.Services.AddScoped<IReportAnalyzerAgent, ReportAnalyzerAgent>();
```

**Referência:** [Agent Framework Setup](./agent-framework-setup.md)

---

## ⏰ Hangfire Jobs

```csharp
public class ReportGeneratorJob(
    IReportScheduleService scheduleService,
    IReportAnalyzerAgent analyzer,
    ILogger<ReportGeneratorJob> logger) : IReportGeneratorJob
{
    [AutomaticRetry(Attempts = 3, DelaysInSeconds = new[] { 60, 300, 900 })]
    [Queue("reports")]
    public async Task ExecuteAsync(int scheduleId, CancellationToken cancellationToken)
    {
        logger.LogInformation("Starting report generation for {ScheduleId}", scheduleId);

        var schedule = await scheduleService.GetByIdAsync(scheduleId);
        if (schedule is null || !schedule.IsActive)
        {
            logger.LogWarning("Schedule {ScheduleId} not found or inactive", scheduleId);
            return;
        }

        // ... implementação
    }
}
```

---

## 🧪 Testes

### Nomenclatura
```csharp
// Classe: {ClasseTestada}Tests
public class ReportServiceTests
{
    // Método: {Método}_{Cenário}_{ResultadoEsperado}
    [Fact]
    public async Task GetByIdAsync_WithValidId_ReturnsReport() { }

    [Fact]
    public async Task GetByIdAsync_WithInvalidId_ThrowsNotFoundException() { }
}
```

### Estrutura AAA
```csharp
[Fact]
public async Task CreateAsync_WithValidData_CreatesSchedule()
{
    // Arrange
    var request = new CreateReportScheduleRequest("Daily Sales", ReportType.Sales);

    // Act
    var result = await _service.CreateAsync(request);

    // Assert
    Assert.NotNull(result);
    Assert.Equal(request.Name, result.Name);
}
```

---

## 🚫 Anti-Patterns a Evitar

```csharp
// ❌ Service Locator
var service = serviceProvider.GetService<IReportService>();

// ✅ Constructor Injection
public class MyClass(IReportService reportService) { }

// ❌ Async void
public async void ProcessReport() { }

// ✅ Async Task
public async Task ProcessReportAsync() { }

// ❌ Catching generic Exception sem log
catch (Exception) { }

// ✅ Log and handle
catch (Exception ex)
{
    _logger.LogError(ex, "Operation failed");
    throw;
}
```
