"use strict";
/**
 * WebSocket Server Generator
 * Generates WebSocket server infrastructure for real-time updates
 */
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.generateWebSocketServer = generateWebSocketServer;
const path = __importStar(require("path"));
async function generateWebSocketServer(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 WebSocket server core
    files.push(generateWebSocketServerCore(backendDir, context.outputDirectory));
    // Generate WebSocket authentication middleware
    files.push(generateWebSocketAuthMiddleware(backendDir, context.outputDirectory));
    // Generate WebSocket event bridge (connects event bus to WebSocket)
    files.push(generateWebSocketEventBridge(backendDir, context.outputDirectory, workflow));
    return { files };
}
/**
 * Generate core WebSocket server implementation
 */
function generateWebSocketServerCore(backendDir, outputDirectory) {
    const content = `/**
 * WebSocket Server Core
 * Provides real-time updates to connected clients
 * DO NOT EDIT MANUALLY
 */

import { Server as WebSocketServer, WebSocket } from 'ws';
import { IncomingMessage } from 'http';
import { parse } from 'url';

export interface WebSocketClient extends WebSocket {
  id: string;
  userId?: string;
  subscriptions: Set<string>;
  isAlive: boolean;
}

export class RealtimeServer {
  private wss: WebSocketServer;
  private clients: Map<string, WebSocketClient> = new Map();
  private heartbeatInterval: NodeJS.Timeout | null = null;

  constructor(server: any) {
    this.wss = new WebSocketServer({
      server,
      path: '/ws',
    });

    this.wss.on('connection', (ws: WebSocket, req: IncomingMessage) => {
      this.handleConnection(ws as WebSocketClient, req);
    });
  }

  /**
   * Handle new WebSocket connection
   */
  private handleConnection(ws: WebSocketClient, req: IncomingMessage): void {
    // Generate client ID
    ws.id = this.generateClientId();
    ws.subscriptions = new Set();
    ws.isAlive = true;

    // Extract user ID from query params or headers (after authentication)
    const query = parse(req.url || '', true).query;
    ws.userId = query.userId as string | undefined;

    // Store client
    this.clients.set(ws.id, ws);

    console.log(\`[WebSocket] Client connected: \${ws.id}\`, { userId: ws.userId, totalClients: this.clients.size });

    // Set up ping/pong for connection health monitoring
    ws.on('pong', () => {
      ws.isAlive = true;
    });

    // Handle incoming messages
    ws.on('message', (data: Buffer) => {
      try {
        const message = JSON.parse(data.toString());
        this.handleMessage(ws, message);
      } catch (error) {
        console.error('[WebSocket] Error parsing message:', error);
        this.sendError(ws, 'Invalid message format');
      }
    });

    // Handle disconnection
    ws.on('close', () => {
      console.log(\`[WebSocket] Client disconnected: \${ws.id}\`);
      this.clients.delete(ws.id);
    });

    ws.on('error', (error) => {
      console.error(\`[WebSocket] Client error: \${ws.id}\`, error);
    });

    // Send welcome message
    this.sendMessage(ws, {
      type: 'connected',
      clientId: ws.id,
    });
  }

  /**
   * Handle incoming message from client
   */
  private handleMessage(ws: WebSocketClient, message: any): void {
    switch (message.type) {
      case 'subscribe':
        this.handleSubscribe(ws, message.eventTypes || []);
        break;

      case 'unsubscribe':
        this.handleUnsubscribe(ws, message.eventTypes || []);
        break;

      case 'ping':
        this.sendMessage(ws, { type: 'pong' });
        break;

      default:
        console.warn(\`[WebSocket] Unknown message type: \${message.type}\`);
        this.sendError(ws, \`Unknown message type: \${message.type}\`);
    }
  }

  /**
   * Subscribe client to event types
   */
  private handleSubscribe(ws: WebSocketClient, eventTypes: string[]): void {
    for (const eventType of eventTypes) {
      ws.subscriptions.add(eventType);
    }

    console.log(\`[WebSocket] Client \${ws.id} subscribed to:\`, eventTypes);

    this.sendMessage(ws, {
      type: 'subscribed',
      eventTypes,
      subscriptions: Array.from(ws.subscriptions),
    });
  }

  /**
   * Unsubscribe client from event types
   */
  private handleUnsubscribe(ws: WebSocketClient, eventTypes: string[]): void {
    for (const eventType of eventTypes) {
      ws.subscriptions.delete(eventType);
    }

    console.log(\`[WebSocket] Client \${ws.id} unsubscribed from:\`, eventTypes);

    this.sendMessage(ws, {
      type: 'unsubscribed',
      eventTypes,
      subscriptions: Array.from(ws.subscriptions),
    });
  }

  /**
   * Broadcast event to all subscribed clients
   */
  broadcast(eventType: string, payload: any, metadata?: { userId?: string; correlationId?: string }): void {
    let sentCount = 0;

    for (const client of this.clients.values()) {
      // Check if client is subscribed to this event type
      if (!client.subscriptions.has(eventType) && !client.subscriptions.has('*')) {
        continue;
      }

      // Optional: Filter by user ID
      if (metadata?.userId && client.userId && client.userId !== metadata.userId) {
        // Skip if event is user-specific and doesn't match
        continue;
      }

      // Send event to client
      if (client.readyState === WebSocket.OPEN) {
        this.sendMessage(client, {
          type: 'event',
          eventType,
          payload,
          timestamp: new Date().toISOString(),
          correlationId: metadata?.correlationId,
        });
        sentCount++;
      }
    }

    console.log(\`[WebSocket] Broadcasted \${eventType} to \${sentCount} clients\`);
  }

  /**
   * Send message to specific client
   */
  private sendMessage(ws: WebSocketClient, message: any): void {
    if (ws.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify(message));
    }
  }

  /**
   * Send error to client
   */
  private sendError(ws: WebSocketClient, error: string): void {
    this.sendMessage(ws, {
      type: 'error',
      error,
    });
  }

  /**
   * Start heartbeat to detect dead connections
   */
  startHeartbeat(intervalMs: number = 30000): void {
    this.heartbeatInterval = setInterval(() => {
      for (const client of this.clients.values()) {
        if (!client.isAlive) {
          console.log(\`[WebSocket] Terminating dead connection: \${client.id}\`);
          client.terminate();
          this.clients.delete(client.id);
          continue;
        }

        client.isAlive = false;
        client.ping();
      }
    }, intervalMs);

    console.log('[WebSocket] Heartbeat started');
  }

  /**
   * Stop heartbeat
   */
  stopHeartbeat(): void {
    if (this.heartbeatInterval) {
      clearInterval(this.heartbeatInterval);
      this.heartbeatInterval = null;
      console.log('[WebSocket] Heartbeat stopped');
    }
  }

  /**
   * Generate unique client ID
   */
  private generateClientId(): string {
    return \`client_\${Date.now()}_\${Math.random().toString(36).substr(2, 9)}\`;
  }

  /**
   * Get connected clients count
   */
  getClientCount(): number {
    return this.clients.size;
  }

  /**
   * Close all connections and shutdown
   */
  close(): void {
    this.stopHeartbeat();

    for (const client of this.clients.values()) {
      client.close(1000, 'Server shutting down');
    }

    this.wss.close(() => {
      console.log('[WebSocket] Server closed');
    });
  }
}

// Singleton instance
let realtimeServerInstance: RealtimeServer | null = null;

/**
 * Initialize WebSocket server
 */
export function initializeRealtimeServer(httpServer: any): RealtimeServer {
  if (!realtimeServerInstance) {
    realtimeServerInstance = new RealtimeServer(httpServer);
    realtimeServerInstance.startHeartbeat();
  }
  return realtimeServerInstance;
}

/**
 * Get WebSocket server instance
 */
export function getRealtimeServer(): RealtimeServer | null {
  return realtimeServerInstance;
}

/**
 * Close WebSocket server
 */
export function closeRealtimeServer(): void {
  if (realtimeServerInstance) {
    realtimeServerInstance.close();
    realtimeServerInstance = null;
  }
}
`;
    return {
        path: path.join(outputDirectory, backendDir, 'src', 'websocket', 'server.ts'),
        content,
    };
}
/**
 * Generate WebSocket authentication middleware
 */
function generateWebSocketAuthMiddleware(backendDir, outputDirectory) {
    const content = `/**
 * WebSocket Authentication Middleware
 * Validates JWT tokens for WebSocket connections
 * DO NOT EDIT MANUALLY
 */

import { IncomingMessage } from 'http';
import { parse } from 'url';
import jwt from 'jsonwebtoken';

export interface AuthenticatedRequest extends IncomingMessage {
  user?: {
    id: string;
    email: string;
    role: string;
  };
}

/**
 * Authenticate WebSocket connection
 * Extracts and validates JWT from query params or headers
 */
export function authenticateWebSocket(req: IncomingMessage): boolean {
  try {
    // Try to get token from query params
    const query = parse(req.url || '', true).query;
    let token = query.token as string | undefined;

    // If not in query, try Authorization header
    if (!token) {
      const authHeader = req.headers.authorization;
      if (authHeader && authHeader.startsWith('Bearer ')) {
        token = authHeader.substring(7);
      }
    }

    if (!token) {
      console.warn('[WebSocket Auth] No token provided');
      return false;
    }

    // Verify token
    const decoded = jwt.verify(token, process.env.JWT_SECRET || 'dev-secret') as any;

    // Attach user to request
    (req as AuthenticatedRequest).user = {
      id: decoded.id || decoded.userId,
      email: decoded.email,
      role: decoded.role || 'user',
    };

    return true;
  } catch (error) {
    console.error('[WebSocket Auth] Authentication failed:', error);
    return false;
  }
}
`;
    return {
        path: path.join(outputDirectory, backendDir, 'src', 'websocket', 'auth.ts'),
        content,
    };
}
/**
 * Generate WebSocket event bridge
 * Connects event bus to WebSocket server
 */
function generateWebSocketEventBridge(backendDir, outputDirectory, workflow) {
    const eventNames = Object.keys(workflow.events);
    const subscriptions = eventNames
        .map(eventName => {
        return `  // Subscribe to ${eventName}
  await eventBus.subscribe('${eventName}', async (event) => {
    const realtimeServer = getRealtimeServer();
    if (realtimeServer) {
      realtimeServer.broadcast('${eventName}', event.payload, {
        userId: event.userId,
        correlationId: event.correlationId,
      });
    }
  });`;
    })
        .join('\n\n');
    const content = `/**
 * WebSocket Event Bridge
 * Connects event bus to WebSocket server for real-time updates
 * DO NOT EDIT MANUALLY
 */

import { getEventBus } from '../events/event-bus';
import { getRealtimeServer } from './server';

/**
 * Initialize event bridge
 * Subscribes to all events and broadcasts them via WebSocket
 */
export async function initializeEventBridge(): Promise<void> {
  const eventBus = getEventBus();

  console.log('[Event Bridge] Initializing event bridge...');

${subscriptions}

  console.log('[Event Bridge] Event bridge initialized');
}
`;
    return {
        path: path.join(outputDirectory, backendDir, 'src', 'websocket', 'event-bridge.ts'),
        content,
    };
}
//# sourceMappingURL=websocket-server-generator.js.map