{%- import "util.jinja.include" as util -%}
{%- set messagegroups = root.messagegroups %}
{%- set uses_cloudevents_message = (root | exists("envelope","CloudEvents/1.0")) %}
{% for messagegroupid, messagegroup in messagegroups.items() -%}
{%- set groupname = messagegroupid  | pascal -%}
{%- set class_name = ( groupname | strip_namespace )+"EventProducer" %}
# {{ project_name | pascal }} - AMQP Producer

Auto-generated AMQP producer for {{ groupname }} message group.

## Overview

This producer provides strongly-typed methods to send events over AMQP using the CloudEvents specification.

## Quick Start

### Using Builder Pattern (Recommended)

```csharp
using {{ project_name | pascal }};
using CloudNative.CloudEvents;
using CloudNative.CloudEvents.SystemTextJson;
using Microsoft.Extensions.Logging;

// Create producer using fluent builder
var producer = {{ class_name }}.CreateBuilder()
    .WithEndpoint("amqp://localhost:5672")
    .WithCredential(new PlainEndpointCredential("username", "password"))
    .WithNode("/queue/events")
    .WithLogger(loggerFactory.CreateLogger<{{ class_name }}>())
    .Build();

// Send events
{%- set first_message = messagegroup.messages.items() | first %}
{%- if first_message %}
{%- set messageid, message = first_message %}
{%- set messagename = messageid | pascal | strip_namespace %}
{%- set message_body_type = util.body_type(data_project_name, root, message) %}
var eventData = new {{ message_body_type }}
{
    // Set properties
};

await producer.Send{{ messagename }}Async(eventData, "tenant-id", "device-id");
{%- endif %}

// Cleanup
producer.Dispose();
```

### Using Factory Method

```csharp
var logger = loggerFactory.CreateLogger<{{ class_name }}>();
var endpoints = new List<Uri> { new Uri("amqp://localhost:5672") };
var options = new Dictionary<string, string> { ["node"] = "/queue/events" };

var producer = {{ class_name }}.CreateProducer(
    logger,
    new PlainEndpointCredential("username", "password"),
    ContentMode.Structured,
    new JsonEventFormatter(),
    options,
    endpoints);
```

## Available Events

This producer can send the following events:

{% for messageid, message in messagegroup.messages.items() -%}
{%- set messagename = messageid | pascal | strip_namespace %}
{%- set message_body_type = util.body_type(data_project_name, root, message) %}
### {{ messagename }}

**Event Type:** `{{ messageid }}`  
**Data Type:** `{{ message_body_type }}`

```csharp
await producer.Send{{ messagename }}Async(data, ...);
```

{% endfor %}

## Authentication

### Plain Credentials

```csharp
var credential = new PlainEndpointCredential("username", "password");
```

### Token-Based Authentication

```csharp
var credential = new TokenEndpointCredential(async () => 
{
    // Fetch and return access token
    return await GetAccessTokenAsync();
});
```

## Advanced Features

### Before Send Hook

Modify or cancel events before sending:

```csharp
producer.BeforeSendAsync += async (cloudEvent, cancellationToken) =>
{
    // Modify event
    cloudEvent.SetAttributeFromString("custom-header", "value");
    
    // Return null to cancel send
    // return null;
    
    // Return event to send
    return cloudEvent;
};
```

### Before Send Hook for Observability

Use the hook to add distributed tracing, logging, and custom metadata:

```csharp
using System.Diagnostics;

producer.BeforeSendAsync += async (cloudEvent, cancellationToken) =>
{
    // Add distributed tracing context
    var activity = Activity.Current;
    if (activity != null)
    {
        cloudEvent["traceparent"] = activity.Id;
        cloudEvent["tracestate"] = activity.TraceStateString ?? "";
    }
    
    // Add correlation ID for log correlation
    var correlationId = Guid.NewGuid().ToString();
    cloudEvent["correlationid"] = correlationId;
    
    // Add custom metadata for monitoring
    cloudEvent["producerversion"] = "1.0.0";
    cloudEvent["environment"] = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "unknown";
    cloudEvent["machinename"] = Environment.MachineName;
    
    // Log outgoing event
    logger.LogInformation("Sending event {EventType} with correlation-id {CorrelationId}", 
        cloudEvent.Type,
        correlationId);
    
    return cloudEvent;
};
```

### CloudEvents Extension Attributes

Add custom CloudEvents extension attributes:

```csharp
producer.BeforeSendAsync += async (cloudEvent, cancellationToken) =>
{
    // Add custom extension attributes
    cloudEvent["tenant"] = tenantId;
    cloudEvent["deviceid"] = deviceId;
    cloudEvent["region"] = "us-west-2";
    cloudEvent["priority"] = "high";
    cloudEvent["batchid"] = batchId;
    
    return cloudEvent;
};
```

### Content Formats

The producer supports multiple CloudEvents formats:

```csharp
// JSON (default)
.WithFormatter(new JsonEventFormatter())

// Protobuf
.WithFormatter(new ProtobufEventFormatter())

// Avro
.WithFormatter(new AvroEventFormatter())
```

### Content Modes

```csharp
// Structured mode (event + data in body)
.WithContentMode(ContentMode.Structured)

// Binary mode (data in body, event in headers)
.WithContentMode(ContentMode.Binary)
```

## Batch Sending

### Send Multiple Events Efficiently

Group related events into a batch for better throughput:

```csharp
{%- set first_message = messagegroup.messages.items() | list | first %}
{%- set messagename = first_message[0] | pascal | strip_namespace %}
{%- set message_body_type = util.body_type(data_project_name, root, first_message[1]) %}
var events = new List<{{ message_body_type }}>
{
    new {{ message_body_type }} { /* ... */ },
    new {{ message_body_type }} { /* ... */ },
    new {{ message_body_type }} { /* ... */ }
};

// Send all events in parallel
var tasks = events.Select(eventData => 
    producer.Send{{ messagename }}Async(eventData, tenantId, deviceId));
await Task.WhenAll(tasks);
```

### Batch with Rate Limiting

Control the rate of concurrent sends to prevent overwhelming the broker:

```csharp
using System.Threading;

public class BatchProducer
{
    private readonly {{ class_name }} _producer;
    private readonly SemaphoreSlim _throttle;
    
    public BatchProducer({{ class_name }} producer, int maxConcurrency = 10)
    {
        _producer = producer;
        _throttle = new SemaphoreSlim(maxConcurrency, maxConcurrency);
    }
    
    {%- set first_message = messagegroup.messages.items() | list | first %}
    {%- set messagename = first_message[0] | pascal | strip_namespace %}
    {%- set message_body_type = util.body_type(data_project_name, root, first_message[1]) %}
    public async Task SendBatchAsync(IEnumerable<{{ message_body_type }}> events, string tenantId, string deviceId)
    {
        var tasks = events.Select(async eventData =>
        {
            await _throttle.WaitAsync();
            try
            {
                await _producer.Send{{ messagename }}Async(eventData, tenantId, deviceId);
            }
            finally
            {
                _throttle.Release();
            }
        });
        
        await Task.WhenAll(tasks);
    }
}
```

### Batch with Backpressure Handling

Monitor queue depth and apply backpressure when necessary:

```csharp
public class BackpressureProducer
{
    private readonly {{ class_name }} _producer;
    private int _pendingCount = 0;
    private readonly int _maxPending = 1000;
    private readonly SemaphoreSlim _backpressure = new SemaphoreSlim(1, 1);
    
    {%- set first_message = messagegroup.messages.items() | list | first %}
    {%- set messagename = first_message[0] | pascal | strip_namespace %}
    {%- set message_body_type = util.body_type(data_project_name, root, first_message[1]) %}
    public async Task Send{{ messagename }}WithBackpressureAsync({{ message_body_type }} data, string tenantId, string deviceId)
    {
        // Wait if too many pending sends
        while (Interlocked.CompareExchange(ref _pendingCount, 0, 0) >= _maxPending)
        {
            await Task.Delay(100);
        }
        
        Interlocked.Increment(ref _pendingCount);
        try
        {
            await _producer.Send{{ messagename }}Async(data, tenantId, deviceId);
        }
        finally
        {
            Interlocked.Decrement(ref _pendingCount);
        }
    }
}
```

## Connection Management

### Connection Pooling

Reuse producer instances across your application:

```csharp
public class ProducerPool
{
    private static readonly Lazy<{{ class_name }}> _instance = new Lazy<{{ class_name }}>(() =>
    {
        var credential = new TokenEndpointCredential(
            async (resource, claims) => await GetTokenAsync(resource, claims));
        
        return {{ class_name }}Builder
            .Create("amqps://example.servicebus.windows.net/inkjet")
            .WithTokenCredential(credential)
            .Build();
    });
    
    public static {{ class_name }} Instance => _instance.Value;
}

// Use from anywhere in your application
await ProducerPool.Instance.Send{{ messagegroup.messages.keys() | first | pascal | strip_namespace }}Async(data, tenantId, deviceId);
```

### Connection Lifecycle Management

Properly manage connection lifecycle in long-running applications:

```csharp
public class ManagedProducer : IDisposable
{
    private {{ class_name }}? _producer;
    private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1);
    private bool _disposed = false;
    
    public async Task<{{ class_name }}> GetProducerAsync()
    {
        if (_producer != null)
            return _producer;
        
        await _lock.WaitAsync();
        try
        {
            if (_producer == null)
            {
                var credential = new TokenEndpointCredential(
                    async (resource, claims) => await GetTokenAsync(resource, claims));
                
                _producer = {{ class_name }}Builder
                    .Create("amqps://example.servicebus.windows.net/inkjet")
                    .WithTokenCredential(credential)
                    .Build();
            }
            return _producer;
        }
        finally
        {
            _lock.Release();
        }
    }
    
    public void Dispose()
    {
        if (!_disposed)
        {
            _producer?.Dispose();
            _lock?.Dispose();
            _disposed = true;
        }
    }
}
```

### Reconnection Strategy

Automatically reconnect on connection failures:

```csharp
public class ResilientProducer : IDisposable
{
    private {{ class_name }}? _producer;
    private readonly string _address;
    private readonly IEndpointCredential _credential;
    private bool _disposed = false;
    
    public ResilientProducer(string address, IEndpointCredential credential)
    {
        _address = address;
        _credential = credential;
    }
    
    private async Task<{{ class_name }}> GetOrRecreateProducerAsync()
    {
        if (_producer != null)
        {
            try
            {
                // Test connection with a no-op
                return _producer;
            }
            catch (AmqpException)
            {
                _producer?.Dispose();
                _producer = null;
            }
        }
        
        // Recreate producer
        _producer = {{ class_name }}Builder
            .Create(_address)
            .WithTokenCredential((TokenEndpointCredential)_credential)
            .Build();
        
        return _producer;
    }
    
    {%- set first_message = messagegroup.messages.items() | list | first %}
    {%- set messagename = first_message[0] | pascal | strip_namespace %}
    {%- set message_body_type = util.body_type(data_project_name, root, first_message[1]) %}
    public async Task Send{{ messagename }}Async({{ message_body_type }} data, string tenantId, string deviceId)
    {
        var producer = await GetOrRecreateProducerAsync();
        await producer.Send{{ messagename }}Async(data, tenantId, deviceId);
    }
    
    public void Dispose()
    {
        if (!_disposed)
        {
            _producer?.Dispose();
            _disposed = true;
        }
    }
}
```

## URI Template Parameters

The producer extracts parameters from the source URI template for routing.

### Extracting Parameters

```csharp
// Parameters are extracted from the source URI template
// Example: /tenant/{tenantid}/device/{deviceid}

{%- set first_message = messagegroup.messages.items() | list | first %}
{%- set messagename = first_message[0] | pascal | strip_namespace %}
// When sending, provide values for each template parameter:
await producer.Send{{ messagename }}Async(
    data,
    tenantId: "contoso",     // Replaces {tenantid}
    deviceId: "device-001"   // Replaces {deviceid}
);

// This produces the routing key: /tenant/contoso/device/device-001
```

### Dynamic Parameter Routing

Use parameters for multi-tenant message routing:

```csharp
public class TenantAwareProducer
{
    private readonly {{ class_name }} _producer;
    
    {%- set first_message = messagegroup.messages.items() | list | first %}
    {%- set messagename = first_message[0] | pascal | strip_namespace %}
    {%- set message_body_type = util.body_type(data_project_name, root, first_message[1]) %}
    public async Task Send{{ messagename }}ForTenantAsync(
        {{ message_body_type }} data,
        string tenantId,
        string deviceId)
    {
        // Parameters route to tenant-specific queues/topics
        await _producer.Send{{ messagename }}Async(data, tenantId, deviceId);
        
        logger.LogInformation("Sent event for tenant {TenantId}, device {DeviceId}", 
            tenantId, deviceId);
    }
}
```

## Configuration Options

| Option | Description | Example |
|--------|-------------|---------|
| `node` | AMQP queue or topic name | `/queue/events` |
| `filter` | Message filter expression | `priority = 'high'` |

```csharp
producer = {{ class_name }}.CreateBuilder()
    .WithNode("/queue/events")
    .WithOption("filter", "priority = 'high'")
    .Build();
```

## Error Handling

### Basic Error Handling

All send methods throw exceptions on failure:

```csharp
try
{
    await producer.Send{{ messagegroup.messages.keys() | first | pascal | strip_namespace }}Async(data, ...);
}
catch (AmqpException ex)
{
    // Handle AMQP-specific errors
    logger.LogError(ex, "Failed to send event");
}
catch (Exception ex)
{
    // Handle general errors
    logger.LogError(ex, "Unexpected error");
}
```

### Retry Logic with Exponential Backoff

Implement automatic retry for transient failures:

```csharp
public class ResilientProducer
{
    private readonly {{ class_name }} _producer;
    private readonly ILogger _logger;
    private readonly int _maxRetries = 5;
    private readonly TimeSpan _initialDelay = TimeSpan.FromSeconds(1);
    private readonly TimeSpan _maxDelay = TimeSpan.FromSeconds(60);

    public async Task Send{{ messagegroup.messages.keys() | first | pascal | strip_namespace }}WithRetryAsync({{ util.body_type(data_project_name, root, messagegroup.messages.values() | first) }} data, string tenantId, string deviceId)
    {
        int retryCount = 0;
        while (true)
        {
            try
            {
                await _producer.Send{{ messagegroup.messages.keys() | first | pascal | strip_namespace }}Async(data, tenantId, deviceId);
                return; // Success
            }
            catch (AmqpException ex) when (retryCount < _maxRetries)
            {
                retryCount++;
                var delay = TimeSpan.FromSeconds(
                    Math.Min(_initialDelay.TotalSeconds * Math.Pow(2, retryCount - 1), _maxDelay.TotalSeconds)
                );
                
                _logger.LogWarning(ex, "Failed to send event, attempt {Attempt}/{MaxRetries}. Retrying in {Delay}s", 
                    retryCount, _maxRetries, delay.TotalSeconds);
                
                await Task.Delay(delay);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to send event after {Attempts} attempts", retryCount);
                throw;
            }
        }
    }
}
```

### Circuit Breaker Pattern

Prevent cascading failures with a circuit breaker:

```csharp
public class CircuitBreakerProducer
{
    private readonly {{ class_name }} _producer;
    private int _failureCount = 0;
    private DateTime _lastFailureTime = DateTime.MinValue;
    private readonly int _failureThreshold = 5;
    private readonly TimeSpan _breakDuration = TimeSpan.FromMinutes(1);

    public async Task Send{{ messagegroup.messages.keys() | first | pascal | strip_namespace }}Async({{ util.body_type(data_project_name, root, messagegroup.messages.values() | first) }} data, string tenantId, string deviceId)
    {
        // Check if circuit is open
        if (_failureCount >= _failureThreshold && 
            (DateTime.UtcNow - _lastFailureTime) < _breakDuration)
        {
            throw new InvalidOperationException("Circuit breaker is open. Too many consecutive failures.");
        }

        try
        {
            await _producer.Send{{ messagegroup.messages.keys() | first | pascal | strip_namespace }}Async(data, tenantId, deviceId);
            _failureCount = 0; // Reset on success
        }
        catch (Exception)
        {
            _failureCount++;
            _lastFailureTime = DateTime.UtcNow;
            throw;
        }
    }
}
```

## Disposal

Always dispose the producer to release AMQP connections:

```csharp
using var producer = {{ class_name }}.CreateBuilder()
    .WithEndpoint("amqp://localhost:5672")
    .WithCredential(credential)
    .Build();

// Use producer
await producer.Send...Async(...);

// Disposed automatically
```

## Dependencies

- `CloudNative.CloudEvents` >= 2.8.0
- `AMQPNetLite` >= 2.4.11
- `Microsoft.Extensions.Logging` >= 9.0.0

## Generated Code

This code was auto-generated by [xRegistry CLI](https://github.com/xregistry/codegen).

**Message Group:** {{ groupname }}  
**Protocol:** AMQP 1.0  
**Envelope:** CloudEvents 1.0
{% endfor %}
