{%- import "cloudevents.jinja.include" as cloudEvents %}
{%- import "util.jinja.include" as util -%}
{%- set messagegroups = root.messagegroups %}
{%- set uses_cloudevents_message = (root | exists("envelope","CloudEvents/1.0")) %}
{%- set uses_plain_kafka_message = (root | exists( "protocol", "kafka" )) %}
{%- 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 { Consumer, EachMessagePayload, KafkaMessage, IHeaders } from 'kafkajs';
{%- if uses_cloudevents_message %}
import { CloudEvent, HTTP } from 'cloudevents';
{%- endif %}
import { Logger, createLogger } from './logger.js';
import { DeserializationError, ProcessingError } from './errors.js';

// 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 uses_cloudevents_message = (messagegroup | exists("envelope","CloudEvents/1.0")) %}
{%- set uses_plain_kafka_message = (messagegroup | existswithout( "binding", "kafka", "format", "cloudevents" )) %}
{%- set messagegroupname = messagegroupid | pascal -%}
{%- set class_name = ( messagegroupname | strip_namespace ) + "EventDispatcher" %}

/**
 * Dispatcher for {{ messagegroupid }} message group
 */
export class {{ class_name }} {
    private logger: Logger;

    constructor(logger?: Logger) {
        this.logger = logger ?? createLogger({ component: '{{ class_name }}' });
    }
    {%- for messageid, message in messagegroup.messages.items() -%}
    {%- set messagename = messageid | strip_namespace | pascal -%}
    {%- set message_body_type = util.body_type(data_project_name, root, message) -%}
    {%- set isCloudEvent = cloudEvents.isCloudEvent(message) %}
    {%- set isKafka = (message | existswithout( "binding", "kafka", "format", "cloudevents" )) %}
    
    {%- if message.description %}
    /**
     * {{ message.description }}
     */
    {%- endif %}
    public {{ messagename }}Handler?: (message: KafkaMessage{% if isCloudEvent %}, cloudEvent: CloudEvent{% endif %}, data: {{ message_body_type }}) => Promise<void>;
    {%- endfor %}
    
    public unhandledMessageHandler?: (payload: EachMessagePayload) => Promise<void>;

    /**
     * Process a message from Kafka
     */
    async processMessage(payload: EachMessagePayload): Promise<boolean> {
        const { topic, partition, message } = payload;
        const messageLogger = this.logger.withContext({ 
            topic, 
            partition, 
            offset: message.offset 
        });
        
        messageLogger.info('Processing message', { 
            timestamp: message.timestamp 
        });
        
        {%- if uses_cloudevents_message %}
        if (this.isCloudEvent(message)) {
            const cloudEvent = this.cloudEventFromMessage(message);
            if (await this.dispatchCloudEvent(message, cloudEvent, messageLogger)) {
                return true;
            }
        }
        {%- else %}
        if (await this.dispatchMessage(message, messageLogger)) {
            return true;
        }
        {%- endif %}
        
        if (this.unhandledMessageHandler) {
            await this.unhandledMessageHandler(payload);
        }
        return false;
    }

    {%- if uses_cloudevents_message %}
    
    /**
     * Check if message is a CloudEvent
     */
    private isCloudEvent(message: KafkaMessage): boolean {
        if (!message.headers) return false;
        
        // Check for structured CloudEvent (content-type header)
        const contentType = this.getHeaderValue(message.headers, 'content-type');
        if (contentType && contentType.startsWith('application/cloudevents')) {
            return true;
        }
        
        // Check for binary CloudEvent (ce- prefixed headers)
        for (const key in message.headers) {
            if (key.toLowerCase().startsWith('ce_') || key.toLowerCase().startsWith('ce-')) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * Extract CloudEvent from Kafka message
     */
    private cloudEventFromMessage(message: KafkaMessage): CloudEvent {
        if (!message.headers) {
            throw new DeserializationError('Message has no headers', { 
                offset: message.offset 
            });
        }
        
        const contentType = this.getHeaderValue(message.headers, 'content-type');
        
        // Structured mode
        if (contentType?.startsWith('application/cloudevents')) {
            if (!message.value) {
                throw new DeserializationError(
                    'CloudEvent structured mode message has no value',
                    { contentType }
                );
            }
            try {
                const eventData = JSON.parse(message.value.toString());
                return new CloudEvent(eventData);
            } catch (error) {
                throw new DeserializationError(
                    'Failed to parse CloudEvent JSON',
                    { error: (error as Error).message }
                );
            }
        }
        
        // Binary mode
        const ceHeaders: { [key: string]: string } = {};
        for (const key in message.headers) {
            const lowerKey = key.toLowerCase();
            if (lowerKey.startsWith('ce_') || lowerKey.startsWith('ce-')) {
                const ceKey = lowerKey.replace(/^ce[_-]/, '');
                ceHeaders[ceKey] = this.getHeaderValue(message.headers, key) || '';
            }
        }
        
        const cloudEvent = new CloudEvent({
            type: ceHeaders['type'] || '',
            source: ceHeaders['source'] || '',
            id: ceHeaders['id'] || '',
            time: ceHeaders['time'],
            datacontenttype: ceHeaders['datacontenttype'] || contentType || 'application/json',
            data: message.value ? JSON.parse(message.value.toString()) : undefined,
            ...ceHeaders
        });
        
        return cloudEvent;
    }
    
    /**
     * Dispatch CloudEvent to appropriate handler
     */
    private async dispatchCloudEvent(message: KafkaMessage, cloudEvent: CloudEvent, logger: Logger): Promise<boolean> {
        const cloudEventType = cloudEvent.type;
        logger.info('Dispatching CloudEvent', { type: cloudEventType, id: cloudEvent.id });
        
        switch (cloudEventType) {
            {%- for messageid, message in messagegroup.messages.items() %}
            {%- set messagename = messageid | pascal %}
            {%- set isCloudEvent = cloudEvents.isCloudEvent(message) %}
            {%- set message_body_type = util.body_type(data_project_name, root, message) -%}
            {%- if "type" in message.envelopemetadata and "value" in message.envelopemetadata["type"] -%}
            case "{{ message.envelopemetadata["type"]["value"] }}":
            {%- else -%}
            case "{{ messageid }}":
            {%- endif %}
                if (this.{{ messagename | strip_namespace }}Handler) {
                    const data = cloudEvent.data as unknown as {{ message_body_type }};
                    await this.{{ messagename | strip_namespace }}Handler(message, cloudEvent, data);
                }
                return true;
            {%- endfor %}
            default:
                return false;
        }
    }
    {%- endif %}

    {%- if uses_plain_kafka_message %}
    
    /**
     * Dispatch plain Kafka message to appropriate handler
     */
    private async dispatchMessage(message: KafkaMessage, logger: Logger): Promise<boolean> {
        const subject = this.getHeaderValue(message.headers, 'subject');
        logger.info('Dispatching message', { subject });
        
        switch (subject) {
            {% for messageid, message in messagegroup.messages.items() if ((message | exists( "protocol", "kafka" )) and not (message | exists("envelope","CloudEvents/1.0"))) -%}
            {%- set messagename = messageid | pascal %}
            {%- set message_body_type = util.body_type(data_project_name, root, message) -%}
            case "{{ messageid }}":
                if (this.{{ messagename | strip_namespace }}Handler) {
                    const contentType = this.getHeaderValue(message.headers, 'content-type') || 'application/json';
                    const data = message.value ? JSON.parse(message.value.toString()) : undefined;
                    await this.{{ messagename | strip_namespace }}Handler(message, data);
                }
                return true;
            {%- endfor %}
            default:
                return false;
        }
    }
    {%- endif %}
    
    /**
     * Get header value from Kafka message headers
     */
    private getHeaderValue(headers: IHeaders | undefined, key: string): string | undefined {
        if (!headers) return undefined;
        
        const header = headers[key];
        if (!header) return undefined;
        
        if (Buffer.isBuffer(header)) {
            return header.toString('utf-8');
        }
        if (Array.isArray(header)) {
            // KafkaJS headers can be arrays; take first element
            const first = header[0];
            if (Buffer.isBuffer(first)) {
                return first.toString('utf-8');
            }
            return first?.toString();
        }
        return header.toString();
    }
}

{% endfor -%}
