"use strict";
/**
 * Event Bus Generator
 * Generates Redis pub/sub event bus infrastructure for event-driven architecture
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateEventBus = generateEventBus;
const path = __importStar(require("path"));
async function generateEventBus(context) {
    const files = [];
    const workflow = context.extendedWorkflow;
    // Skip if no extended workflow or no events defined
    if (!workflow?.events || Object.keys(workflow.events).length === 0) {
        return { files };
    }
    // Skip if no Redis cache configured
    if (!workflow.technology?.cache || !workflow.technology.cache.provider.includes('redis')) {
        return { files };
    }
    const backendDir = workflow.technology?.backend?.directory || 'backend';
    // Generate event bus core
    files.push(generateEventBusCore(backendDir, context.outputDirectory));
    // Generate event emitter
    files.push(generateEventEmitter(backendDir, context.outputDirectory));
    // Generate event subscriber
    files.push(generateEventSubscriber(backendDir, context.outputDirectory));
    // Generate event middleware
    files.push(generateEventMiddleware(backendDir, context.outputDirectory));
    return { files };
}
/**
 * Generate core event bus implementation
 */
function generateEventBusCore(backendDir, outputDirectory) {
    const content = `/**
 * Event Bus Core
 * Redis pub/sub based event bus for distributed event-driven architecture
 * DO NOT EDIT MANUALLY
 */

import { Redis } from 'ioredis';
import { Event, EventType, EventPayloadMap } from '../types/events';

export interface EventBusConfig {
  redis: {
    host: string;
    port: number;
    password?: string;
    db?: number;
  };
  channelPrefix?: string;
}

export type EventHandler<T extends EventType = EventType> = (
  event: Extract<Event, { type: T }>
) => Promise<void> | void;

export class EventBus {
  private publisher: Redis;
  private subscriber: Redis;
  private handlers: Map<EventType, Set<EventHandler>> = new Map();
  private channelPrefix: string;
  private isInitialized = false;

  constructor(config: EventBusConfig) {
    this.channelPrefix = config.channelPrefix || 'events:';

    this.publisher = new Redis({
      host: config.redis.host,
      port: config.redis.port,
      password: config.redis.password,
      db: config.redis.db || 0,
    });

    this.subscriber = new Redis({
      host: config.redis.host,
      port: config.redis.port,
      password: config.redis.password,
      db: config.redis.db || 0,
    });
  }

  /**
   * Initialize event bus and set up message handlers
   */
  async initialize(): Promise<void> {
    if (this.isInitialized) {
      return;
    }

    this.subscriber.on('message', (channel: string, message: string) => {
      this.handleMessage(channel, message).catch(error => {
        console.error('[EventBus] Error handling message:', error);
      });
    });

    this.subscriber.on('error', (error: Error) => {
      console.error('[EventBus] Subscriber error:', error);
    });

    this.publisher.on('error', (error: Error) => {
      console.error('[EventBus] Publisher error:', error);
    });

    this.isInitialized = true;
  }

  /**
   * Publish an event to the event bus
   */
  async publish<T extends EventType>(
    eventType: T,
    payload: EventPayloadMap[T],
    metadata?: { correlationId?: string; userId?: string }
  ): Promise<void> {
    const event: Event = {
      type: eventType,
      payload,
      timestamp: new Date().toISOString(),
      correlationId: metadata?.correlationId,
      userId: metadata?.userId,
    } as Event;

    const channel = this.getChannel(eventType);
    const message = JSON.stringify(event);

    await this.publisher.publish(channel, message);
    console.log(\`[EventBus] Published event: \${eventType}\`, { correlationId: metadata?.correlationId });
  }

  /**
   * Subscribe to an event type
   */
  async subscribe<T extends EventType>(
    eventType: T,
    handler: EventHandler<T>
  ): Promise<void> {
    if (!this.isInitialized) {
      await this.initialize();
    }

    // Add handler to internal map
    if (!this.handlers.has(eventType)) {
      this.handlers.set(eventType, new Set());
      // Subscribe to Redis channel
      const channel = this.getChannel(eventType);
      await this.subscriber.subscribe(channel);
      console.log(\`[EventBus] Subscribed to: \${eventType}\`);
    }

    this.handlers.get(eventType)!.add(handler as EventHandler);
  }

  /**
   * Unsubscribe from an event type
   */
  async unsubscribe<T extends EventType>(
    eventType: T,
    handler?: EventHandler<T>
  ): Promise<void> {
    const handlers = this.handlers.get(eventType);
    if (!handlers) return;

    if (handler) {
      handlers.delete(handler as EventHandler);

      // If no more handlers, unsubscribe from Redis
      if (handlers.size === 0) {
        const channel = this.getChannel(eventType);
        await this.subscriber.unsubscribe(channel);
        this.handlers.delete(eventType);
        console.log(\`[EventBus] Unsubscribed from: \${eventType}\`);
      }
    } else {
      // Unsubscribe all handlers for this event type
      const channel = this.getChannel(eventType);
      await this.subscriber.unsubscribe(channel);
      this.handlers.delete(eventType);
      console.log(\`[EventBus] Unsubscribed from: \${eventType}\`);
    }
  }

  /**
   * Handle incoming message from Redis
   */
  private async handleMessage(channel: string, message: string): Promise<void> {
    try {
      const event = JSON.parse(message) as Event;
      const eventType = this.getEventTypeFromChannel(channel);

      const handlers = this.handlers.get(eventType);
      if (!handlers || handlers.size === 0) {
        console.warn(\`[EventBus] No handlers for event: \${eventType}\`);
        return;
      }

      // Execute all handlers for this event type
      await Promise.all(
        Array.from(handlers).map(handler =>
          Promise.resolve(handler(event as any)).catch(error => {
            console.error(\`[EventBus] Handler error for \${eventType}:\`, error);
          })
        )
      );

      console.log(\`[EventBus] Handled event: \${eventType}\`, { correlationId: event.correlationId });
    } catch (error) {
      console.error('[EventBus] Error parsing message:', error);
    }
  }

  /**
   * Get Redis channel name for an event type
   */
  private getChannel(eventType: EventType): string {
    return \`\${this.channelPrefix}\${eventType}\`;
  }

  /**
   * Extract event type from Redis channel name
   */
  private getEventTypeFromChannel(channel: string): EventType {
    return channel.replace(this.channelPrefix, '') as EventType;
  }

  /**
   * Close connections
   */
  async close(): Promise<void> {
    await this.subscriber.quit();
    await this.publisher.quit();
    this.isInitialized = false;
    console.log('[EventBus] Closed connections');
  }
}

// Singleton instance
let eventBusInstance: EventBus | null = null;

/**
 * Initialize and get event bus instance
 */
export function getEventBus(config?: EventBusConfig): EventBus {
  if (!eventBusInstance) {
    if (!config) {
      throw new Error('EventBus config required for first initialization');
    }
    eventBusInstance = new EventBus(config);
  }
  return eventBusInstance;
}

/**
 * Close event bus connection
 */
export async function closeEventBus(): Promise<void> {
  if (eventBusInstance) {
    await eventBusInstance.close();
    eventBusInstance = null;
  }
}
`;
    return {
        path: path.join(outputDirectory, backendDir, 'src', 'events', 'event-bus.ts'),
        content,
    };
}
/**
 * Generate event emitter utility
 */
function generateEventEmitter(backendDir, outputDirectory) {
    const content = `/**
 * Event Emitter Utility
 * Helper functions for emitting events from API endpoints
 * DO NOT EDIT MANUALLY
 */

import { getEventBus } from './event-bus';
import { EventType, EventPayloadMap } from '../types/events';

export interface EmitEventOptions {
  correlationId?: string;
  userId?: string;
}

/**
 * Emit an event through the event bus
 */
export async function emitEvent<T extends EventType>(
  eventType: T,
  payload: EventPayloadMap[T],
  options?: EmitEventOptions
): Promise<void> {
  try {
    const eventBus = getEventBus();
    await eventBus.publish(eventType, payload, {
      correlationId: options?.correlationId,
      userId: options?.userId,
    });
  } catch (error) {
    console.error(\`Failed to emit event: \${eventType}\`, error);
    // Don't throw - event emission failures shouldn't break the API
  }
}

/**
 * Express middleware to extract correlation ID and user ID
 */
export function eventContextMiddleware(req: any, res: any, next: any): void {
  // Attach correlation ID (from header or generate new)
  req.correlationId = req.headers['x-correlation-id'] || \`\${Date.now()}-\${Math.random().toString(36).substr(2, 9)}\`;

  // Attach user ID from auth context if available
  req.userId = req.user?.id || req.user?.userId;

  next();
}

/**
 * Helper to emit event with request context
 */
export async function emitEventWithContext<T extends EventType>(
  req: any,
  eventType: T,
  payload: EventPayloadMap[T]
): Promise<void> {
  await emitEvent(eventType, payload, {
    correlationId: req.correlationId,
    userId: req.userId,
  });
}
`;
    return {
        path: path.join(outputDirectory, backendDir, 'src', 'events', 'event-emitter.ts'),
        content,
    };
}
/**
 * Generate event subscriber utility
 */
function generateEventSubscriber(backendDir, outputDirectory) {
    const content = `/**
 * Event Subscriber Utility
 * Helper functions for subscribing to events and defining handlers
 * DO NOT EDIT MANUALLY
 */

import { getEventBus } from './event-bus';
import { EventType, EventHandler } from '../types/events';

export interface SubscriberConfig {
  eventType: EventType;
  handler: EventHandler;
  name?: string;
}

/**
 * Subscribe to an event with a handler
 */
export async function subscribeToEvent<T extends EventType>(
  config: SubscriberConfig
): Promise<void> {
  const eventBus = getEventBus();
  await eventBus.subscribe(config.eventType as any, config.handler as any);

  const handlerName = config.name || 'anonymous';
  console.log(\`[EventSubscriber] Registered handler '\${handlerName}' for event: \${config.eventType}\`);
}

/**
 * Initialize all event subscribers
 * Call this function on application startup
 */
export async function initializeEventSubscribers(
  subscribers: SubscriberConfig[]
): Promise<void> {
  console.log(\`[EventSubscriber] Initializing \${subscribers.length} event subscribers...\`);

  for (const subscriber of subscribers) {
    await subscribeToEvent(subscriber);
  }

  console.log('[EventSubscriber] All event subscribers initialized');
}

/**
 * Gracefully shutdown event subscribers
 */
export async function shutdownEventSubscribers(): Promise<void> {
  const { closeEventBus } = await import('./event-bus');
  await closeEventBus();
  console.log('[EventSubscriber] Event subscribers shut down');
}
`;
    return {
        path: path.join(outputDirectory, backendDir, 'src', 'events', 'event-subscriber.ts'),
        content,
    };
}
/**
 * Generate event middleware
 */
function generateEventMiddleware(backendDir, outputDirectory) {
    const content = `/**
 * Event Middleware
 * Express middleware for event-driven features
 * DO NOT EDIT MANUALLY
 */

import { Request, Response, NextFunction } from 'express';

/**
 * Attach event context to request
 * Generates correlation ID and extracts user ID
 */
export function eventContextMiddleware(
  req: Request,
  res: Response,
  next: NextFunction
): void {
  // Generate or use existing correlation ID
  const correlationId =
    req.headers['x-correlation-id'] as string ||
    \`\${Date.now()}-\${Math.random().toString(36).substr(2, 9)}\`;

  // Attach correlation ID to request
  (req as any).correlationId = correlationId;

  // Add correlation ID to response headers
  res.setHeader('x-correlation-id', correlationId);

  // Extract user ID from auth context if available
  const user = (req as any).user;
  (req as any).userId = user?.id || user?.userId;

  next();
}

/**
 * Error handler for event publishing failures
 * Logs errors but doesn't block the request
 */
export function eventErrorHandler(
  err: Error,
  req: Request,
  res: Response,
  next: NextFunction
): void {
  if (err.message?.includes('event') || err.message?.includes('EventBus')) {
    console.error('[EventMiddleware] Event error (non-blocking):', err);
    // Don't send error response - events are async and optional
    next();
  } else {
    next(err);
  }
}
`;
    return {
        path: path.join(outputDirectory, backendDir, 'src', 'middleware', 'event-middleware.ts'),
        content,
    };
}
//# sourceMappingURL=event-bus-generator.js.map