{%- import "cloudevents.jinja.include" as cloudEvents -%}
{%- import "util.jinja.include" as util -%}
{%- set data_module_name = data_project_name | strip_invalid_identifier_characters %}
// This code was generated by the xRegistry tool.
// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.

import * as rhea from 'rhea';
{%- set messagegroups = root.messagegroups %}
{%- set uses_cloudevents_message = cloudEvents.usesCloudEvents(root) %}
{%- if uses_cloudevents_message %}
import { CloudEvent } from 'cloudevents';
{%- endif %}
import { v4 as uuidv4 } from 'uuid';

// Import data types (need to compile data project first: cd ../{{ data_project_name }} && npm install && npm run build)
// NOTE: In real usage, the data project should be a proper npm package dependency
import * as {{ data_module_name }} from '../../{{ data_project_name }}/dist/index.js';

{% for messagegroupid, messagegroup in messagegroups.items() -%}
{%- set groupname = messagegroupid | pascal -%}
{%- set class_name = (groupname | strip_namespace) + "Producer" %}

/**
 * Producer class to send messages in the `{{ messagegroupid }}` message group.
 */
export class {{ class_name }} {
    private connection: rhea.Connection;
    private sender: rhea.Sender;
    private mode: 'binary' | 'structured';
    private format: string;
    
    /**
     * Constructor
     * @param host The AMQP host
     * @param port The AMQP port
     * @param address The AMQP address (queue or topic)
     * @param username Optional username for authentication
     * @param password Optional password for authentication
     * @param mode CloudEvents content mode: 'binary' or 'structured' (default: 'structured')
     * @param format Content type format for structured mode (default: 'application/json')
     */
    constructor(
        host: string,
        port: number = 5672,
        address: string,
        username?: string,
        password?: string,
        mode: 'binary' | 'structured' = 'structured',
        format: string = 'application/json'
    ) {
        this.mode = mode;
        this.format = format;
        const connectionOptions: rhea.ConnectionOptions = {
            host: host,
            port: port
        };
        
        if (username && password) {
            connectionOptions.username = username;
            connectionOptions.password = password;
        }
        
        // Type assertion for rhea library's missing type definition
        this.connection = (rhea as unknown as { connect(opts: rhea.ConnectionOptions): rhea.Connection }).connect(connectionOptions);
        this.sender = this.connection.open_sender({ target: { address: address } });
    }
    
    {% for messageid, message in messagegroup.messages.items() -%}
    {%- set messagename = messageid | pascal | strip_namespace %}
    {%- set isCloudEvent = cloudEvents.isCloudEvent(message) %}
    {%- set type_name = util.body_type(data_project_name, root, message) %}
    
    /**
     * Send the `{{ messagename }}` message
     {%- if message.description %}
     * {{ message.description }}
     {%- endif %}
     * @param data The message data object
     {%- if isCloudEvent %}
     {%- for attrname, attribute in message.envelopemetadata.items() if attribute.type == "uritemplate" %}
     {%- for placeholder in attribute.value | regex_search('\\{([A-Za-z0-9_]+)\\}') %}
     * @param {{ placeholder }} Value for placeholder {{ placeholder }} in attribute {{ attrname }}
     {%- endfor %}
     {%- endfor %}
     {%- for attrname, attribute in message.envelopemetadata.items() if attribute.value is not defined and attrname not in ["time", "id", "datacontenttype", "dataschema"] %}
     * @param {{ attrname }} CloudEvent {{ attrname }} attribute{% if not attribute.required %} (optional){% endif %}
     {%- endfor %}
     {%- endif %}
     * @param contentType The content type of the message data
     * @returns Promise that resolves when the message is sent
     */
    async send{{ messagename }}(
        data: {{ type_name }},
        {%- if isCloudEvent %}
        {%- for attrname, attribute in message.envelopemetadata.items() if attribute.type == "uritemplate" %}
        {%- for placeholder in attribute.value | regex_search('\\{([A-Za-z0-9_]+)\\}') %}
        {{ placeholder }}: string,
        {%- endfor %}
        {%- endfor %}
        {%- for attrname, attribute in message.envelopemetadata.items() if attribute.value is not defined and attrname not in ["time", "id", "datacontenttype", "dataschema"] %}
        {{ attrname }}{% if not attribute.required %}?{% endif %}: string,
        {%- endfor %}
        {%- endif %}
        contentType: string = 'application/json'
    ): Promise<void> {
        return new Promise((resolve, reject) => {
            {%- if isCloudEvent %}
            const cloudEvent = new CloudEvent({
            {%- for attrname, attribute in message.envelopemetadata.items() %}
            {%- if attrname == "type" %}
                type: {% if attribute.value %}'{{ attribute.value }}'{% else %}{{ attrname }}{% endif %},
            {%- elif attrname == "source" %}
                source: {% if attribute.value %}{% if attribute.type == "uritemplate" %}`{{ attribute.value | regex_replace('\\{([A-Za-z0-9_]+)\\}', '${\\1}') }}`{% else %}'{{ attribute.value }}'{% endif %}{% else %}{{ attrname }} || '/producer'{% endif %},
            {%- elif attrname in ["id", "time"] %}
                {# id and time are auto-generated #}
            {%- elif attrname == "datacontenttype" %}
                {# handled separately #}
            {%- elif attrname == "dataschema" %}
                {# handled separately if needed #}
            {%- elif attribute.value %}
                {{ attrname }}: {% if attribute.type == "uritemplate" %}`{{ attribute.value | regex_replace('\\{([A-Za-z0-9_]+)\\}', '${\\1}') }}`{% else %}'{{ attribute.value }}'{% endif %},
            {%- else %}
                {% if not attribute.required %}...({{ attrname }} && { {{ attrname }} }),{% else %}{{ attrname }},{% endif %}
            {%- endif %}
            {%- endfor %}
                id: uuidv4(),
                data: data,
                datacontenttype: contentType
            });
            
            let message: rhea.Message;
            if (this.mode === 'structured') {
                message = {
                    body: cloudEvent.toJSON(),
                    content_type: this.format
                };
            } else {
                const application_properties: Record<string, any> = {
                    ce_specversion: '1.0',
                    ce_id: cloudEvent.id,
                    ce_type: cloudEvent.type,
                    ce_source: cloudEvent.source
                };
                if (cloudEvent.subject) application_properties.ce_subject = cloudEvent.subject;
                if (cloudEvent.time) application_properties.ce_time = cloudEvent.time;
                {%- for attrname, attribute in message.envelopemetadata.items() if attrname not in ["id", "type", "source", "subject", "time", "specversion", "datacontenttype", "dataschema", "data"] %}
                if ((cloudEvent as any).{{ attrname }}) application_properties.ce_{{ attrname }} = (cloudEvent as any).{{ attrname }};
                {%- endfor %}
                message = {
                    body: data,
                    content_type: contentType,
                    application_properties
                };
            }
            {%- else %}
            const message: rhea.Message = {
                body: data,
                application_properties: {
                    subject: '{{ messageid }}'
                },
                content_type: contentType
            };
            {%- endif %}
            
            this.sender.send(message);
            resolve();
        });
    }
    
    /**
     * Send multiple `{{ messagename }}` messages
     * @param dataArray Array of message data objects
     {%- if isCloudEvent %}
     {%- for attrname, attribute in message.envelopemetadata.items() if attribute.type == "uritemplate" %}
     {%- for placeholder in attribute.value | regex_search('\\{([A-Za-z0-9_]+)\\}') %}
     * @param {{ placeholder }} Value for placeholder {{ placeholder }} in attribute {{ attrname }}
     {%- endfor %}
     {%- endfor %}
     {%- for attrname, attribute in message.envelopemetadata.items() if attribute.value is not defined and attrname not in ["time", "id", "datacontenttype", "dataschema"] %}
     * @param {{ attrname }} CloudEvent {{ attrname }} attribute{% if not attribute.required %} (optional){% endif %}
     {%- endfor %}
     {%- endif %}
     * @param contentType The content type of the message data
     * @returns Promise that resolves when all messages are sent
     */
    async send{{ messagename }}Batch(
        dataArray: {{ type_name }}[],
        {%- if isCloudEvent %}
        {%- for attrname, attribute in message.envelopemetadata.items() if attribute.type == "uritemplate" %}
        {%- for placeholder in attribute.value | regex_search('\\{([A-Za-z0-9_]+)\\}') %}
        {{ placeholder }}: string,
        {%- endfor %}
        {%- endfor %}
        {%- for attrname, attribute in message.envelopemetadata.items() if attribute.value is not defined and attrname not in ["time", "id", "datacontenttype", "dataschema"] %}
        {{ attrname }}{% if not attribute.required %}?{% endif %}: string,
        {%- endfor %}
        {%- endif %}
        contentType: string = 'application/json'
    ): Promise<void> {
        for (const data of dataArray) {
            await this.send{{ messagename }}(data, {%- if isCloudEvent %}
{%- for attrname, attribute in message.envelopemetadata.items() if attribute.type == "uritemplate" %}
{%- for placeholder in attribute.value | regex_search('\\{([A-Za-z0-9_]+)\\}') %}{{ placeholder }}, {% endfor %}
{%- endfor %}
{%- for attrname, attribute in message.envelopemetadata.items() if attribute.value is not defined and attrname not in ["time", "id", "datacontenttype", "dataschema"] %}{{ attrname }}, {% endfor %}
{%- endif %}contentType);
        }
    }
    {% endfor %}
    
    /**
     * Close the producer
     */
    async close(): Promise<void> {
        return new Promise((resolve) => {
            this.sender.close();
            this.connection.close();
            resolve();
        });
    }
}
{% endfor %}
