/**
 * Phase 4 Pipeline -- Stage 5: HTTP Server Generator
 *
 * Generates Cloud Run-compatible HTTP server code for the resolved implementation
 * language and framework. Produces main entrypoint, route handlers, middleware,
 * health check endpoints, DI factory, and Zod validation schemas derived from
 * Phase 2 bounded context definitions.
 *
 * TypeScript target: Hono framework with structured JSON logging.
 * Rust target: Axum framework with Tower middleware layers.
 *
 * All generated files carry a "do not edit manually" header.
 *
 * Exports:
 *   - generateHttpServer(context) -- pipeline entry point
 *
 * Error codes:
 *   ECLI-P4-050  Missing prerequisite stage output (erp-target-resolution)
 *   ECLI-P4-051  HTTP server file write failure
 */
import { mkdirSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { hasTemplateSupport } from '../types.js';
import { createSpan, endSpan, emitSpan } from '../../phase2/telemetry.js';
// ============================================================================
// Constants
// ============================================================================
const DIR_MODE = 0o700;
const FILE_MODE = 0o600;
const GENERATED_HEADER_TS = '// Generated by Phase 4 pipeline — do not edit manually\n';
const GENERATED_HEADER_RS = '// Generated by Phase 4 pipeline — do not edit manually\n';
// ============================================================================
// Name Helpers
// ============================================================================
function slugify(name) {
    return name
        .replace(/([a-z])([A-Z])/g, '$1-$2')
        .replace(/[^a-zA-Z0-9]+/g, '-')
        .replace(/^-|-$/g, '')
        .toLowerCase();
}
function snakeCase(name) {
    return slugify(name).replace(/-/g, '_');
}
function pascalCase(name) {
    return name
        .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))
        .replace(/^(.)/, (_, c) => c.toUpperCase());
}
function camelCase(name) {
    const p = pascalCase(name);
    return p.charAt(0).toLowerCase() + p.slice(1);
}
// ============================================================================
// TypeScript / Hono Generation — Domain Logic Routes
// ============================================================================
/**
 * Generate the TypeScript main.ts entrypoint for Hono on Cloud Run.
 */
function tsMain() {
    return [
        GENERATED_HEADER_TS,
        `import { serve } from '@hono/node-server';`,
        `import { app } from './routes.js';`,
        '',
        'const PORT = parseInt(process.env.PORT ?? \'8080\', 10);',
        '',
        'serve({ fetch: app.fetch, port: PORT }, (info) => {',
        '  process.stdout.write(',
        '    JSON.stringify({',
        '      severity: \'INFO\',',
        '      message: \'HTTP server started\',',
        '      port: info.port,',
        '      pid: process.pid,',
        '      timestamp: new Date().toISOString(),',
        '    }) + \'\\n\',',
        '  );',
        '});',
        '',
        'process.on(\'SIGTERM\', () => {',
        '  process.stdout.write(',
        '    JSON.stringify({',
        '      severity: \'INFO\',',
        '      message: \'Received SIGTERM — shutting down gracefully\',',
        '      timestamp: new Date().toISOString(),',
        '    }) + \'\\n\',',
        '  );',
        '  process.exit(0);',
        '});',
        '',
    ].join('\n');
}
/**
 * Generate Zod validation schemas dynamically from DDD bounded contexts.
 * One schema per command (POST body) and one per query (GET params).
 */
/**
 * Infer the Zod schema type for a field based on its name.
 * Returns the z.* expression as a string.
 */
function inferZodType(fieldName, required) {
    const lower = fieldName.toLowerCase();
    const opt = required ? '' : '.optional()';
    // Numeric fields
    if (/amount|price|cost|fee|rate|total|balance|value|score|rating|weight|percentage|pct/.test(lower))
        return `z.coerce.number()${opt}`;
    if (/quantity|count|number|version|level|priority|minutes|hours|days|seconds|attempts|retries/.test(lower))
        return `z.coerce.number().int()${opt}`;
    // Boolean fields
    if (/^is[A-Z_]|^has[A-Z_]|enabled|active|valid|flag|approved|completed|cancelled|deleted/.test(fieldName))
        return `z.coerce.boolean()${opt}`;
    // Date/time fields
    if (/date|deadline|expires|timestamp|_at$|created|updated|scheduled/.test(lower))
        return `z.string().datetime({ offset: true })${opt || '.optional()'}`;
    // Email/URL
    if (/email/.test(lower))
        return `z.string().email()${opt}`;
    if (/url|link|uri|endpoint/.test(lower))
        return `z.string().url()${opt}`;
    // ID fields — required string with min length
    if (/id$|_id$|ref$|uuid|key/.test(lower))
        return required ? `z.string().min(1)` : `z.string().optional()`;
    // Default: string
    return required ? `z.string().min(1)` : `z.string().optional()`;
}
/**
 * Infer typed default fields for a command when the DDD model has no explicit input fields.
 * Uses the command name and context name to produce domain-relevant Zod fields.
 */
function inferDefaultCommandFields(cmdName, contextName) {
    const lower = cmdName.toLowerCase();
    const ctxLower = contextName.toLowerCase();
    const entityId = `${snakeCase(contextName)}_id`;
    // Base fields every command needs
    const fields = [`  ${entityId}: z.string().uuid(),`];
    // Add fields based on command verb
    if (/^create|^add|^register|^submit/.test(lower)) {
        fields.push(`  name: z.string().min(1).max(255),`);
        fields.push(`  description: z.string().max(2000).optional(),`);
        fields.push(`  metadata: z.record(z.unknown()).optional(),`);
    }
    else if (/^update|^modify|^patch|^edit/.test(lower)) {
        fields.push(`  updates: z.record(z.unknown()),`);
        fields.push(`  reason: z.string().min(1).optional(),`);
    }
    else if (/^delete|^remove|^cancel|^deactivate/.test(lower)) {
        fields.push(`  reason: z.string().min(1).optional(),`);
    }
    else if (/^connect|^sync|^integrate|^link/.test(lower)) {
        fields.push(`  source_system: z.string().min(1),`);
        fields.push(`  connection_config: z.record(z.unknown()),`);
    }
    else {
        fields.push(`  payload: z.record(z.unknown()),`);
    }
    // Add domain-specific fields based on context
    if (/supply.chain|procurement|inventory|warehouse/.test(ctxLower)) {
        fields.push(`  facility_id: z.string().min(1).optional(),`);
        fields.push(`  quantity: z.coerce.number().positive().optional(),`);
    }
    if (/carbon|emission|sustainability|esg/.test(ctxLower)) {
        fields.push(`  emission_factor: z.coerce.number().nonnegative().optional(),`);
        fields.push(`  reporting_period: z.string().datetime({ offset: true }).optional(),`);
    }
    if (/sap|erp|financial/.test(ctxLower)) {
        fields.push(`  company_code: z.string().min(1).optional(),`);
        fields.push(`  fiscal_year: z.coerce.number().int().optional(),`);
    }
    fields.push(`  correlation_id: z.string().uuid().optional(),`);
    return fields;
}
/**
 * Infer typed default query fields when the DDD model has no explicit parameters.
 */
function inferDefaultQueryFields(contextName) {
    const ctxLower = contextName.toLowerCase();
    const fields = [
        `  status: z.enum(['active', 'inactive', 'pending', 'completed', 'error']).optional(),`,
        `  created_after: z.string().datetime({ offset: true }).optional(),`,
        `  created_before: z.string().datetime({ offset: true }).optional(),`,
        `  search: z.string().max(500).optional(),`,
    ];
    if (/supply.chain|procurement|inventory|warehouse/.test(ctxLower)) {
        fields.push(`  facility_id: z.string().optional(),`);
        fields.push(`  supplier_code: z.string().optional(),`);
    }
    if (/carbon|emission|sustainability|esg/.test(ctxLower)) {
        fields.push(`  scope: z.enum(['scope_1', 'scope_2', 'scope_3']).optional(),`);
        fields.push(`  reporting_period: z.string().optional(),`);
    }
    if (/sap|erp|financial/.test(ctxLower)) {
        fields.push(`  company_code: z.string().optional(),`);
    }
    return fields;
}
function tsSchemas(contexts) {
    const lines = [
        GENERATED_HEADER_TS,
        `import { z } from 'zod';`,
        '',
    ];
    for (const ctx of contexts) {
        const ctxLabel = pascalCase(ctx.name);
        lines.push(`// === ${ctxLabel} Schemas ===`);
        lines.push('');
        // Command schemas (POST body) — fields are required by default
        for (const cmd of ctx.commands) {
            const schemaName = `${pascalCase(cmd.name)}Schema`;
            const fields = (cmd.input ?? []).length > 0
                ? (cmd.input ?? []).map(f => {
                    const fieldName = snakeCase(f);
                    return `  ${fieldName}: ${inferZodType(f, true)},`;
                })
                : inferDefaultCommandFields(cmd.name, ctx.name);
            lines.push(`export const ${schemaName} = z.object({`);
            lines.push(...fields);
            lines.push('});');
            lines.push(`export type ${pascalCase(cmd.name)}Input = z.infer<typeof ${schemaName}>;`);
            lines.push('');
        }
        // Query schemas (GET params) — fields are optional (query filters)
        for (const q of ctx.queries) {
            const schemaName = `${pascalCase(q.name)}Schema`;
            const fields = (q.parameters ?? []).length > 0
                ? (q.parameters ?? []).map(f => {
                    const fieldName = snakeCase(f);
                    return `  ${fieldName}: ${inferZodType(f, false)},`;
                })
                : inferDefaultQueryFields(ctx.name);
            lines.push(`export const ${schemaName} = z.object({`);
            lines.push(...fields);
            lines.push('  limit: z.coerce.number().int().positive().default(50),');
            lines.push('  offset: z.coerce.number().int().nonnegative().default(0),');
            lines.push('});');
            lines.push(`export type ${pascalCase(q.name)}Params = z.infer<typeof ${schemaName}>;`);
            lines.push('');
        }
    }
    return lines.join('\n');
}
/**
 * Generate the DI factory module dynamically from DDD bounded contexts.
 * One port interface + adapter per context, plus a shared AuditPort.
 */
function tsDependencies(contexts) {
    const lines = [
        GENERATED_HEADER_TS,
        `import type { DbClient, ErpClient } from '../infra/clients.js';`,
        `import { ConsoleTelemetry } from '../infra/telemetry.js';`,
        `import type { Telemetry } from '../infra/telemetry.js';`,
        '',
        '// ============================================================================',
        '// Audit Port (shared across all contexts)',
        '// ============================================================================',
        '',
        'export interface AuditEntry {',
        '  id: string;',
        '  entity_type: string;',
        '  entity_id: string;',
        '  action: string;',
        '  actor: string;',
        '  payload: unknown;',
        '  created_at: string;',
        '}',
        '',
        'export interface IAuditPort {',
        '  log(entityType: string, entityId: string, action: string, actor: string, payload: unknown): Promise<AuditEntry>;',
        '}',
        '',
        'class AuditAdapter implements IAuditPort {',
        '  constructor(private readonly db: DbClient) {}',
        '',
        '  async log(entityType: string, entityId: string, action: string, actor: string, payload: unknown): Promise<AuditEntry> {',
        '    const id = crypto.randomUUID();',
        '    const now = new Date().toISOString();',
        '    await this.db.execute(',
        '      \'INSERT INTO audit_log (id, entity_type, entity_id, action, actor, payload, created_at) VALUES (:1, :2, :3, :4, :5, :6, :7)\',',
        '      [id, entityType, entityId, action, actor, JSON.stringify(payload), now],',
        '    );',
        '    return { id, entity_type: entityType, entity_id: entityId, action, actor, payload, created_at: now };',
        '  }',
        '}',
        '',
    ];
    // Per-context port interfaces and adapters
    for (const ctx of contexts) {
        const ctxPascal = pascalCase(ctx.name);
        const ctxSnake = snakeCase(ctx.name);
        const tableName = `${ctxSnake}_records`;
        lines.push(`// === ${ctxPascal} Port ===`);
        lines.push('');
        lines.push(`export interface ${ctxPascal}Record {`);
        lines.push('  id: string;');
        lines.push('  status: string;');
        lines.push('  created_at: string;');
        lines.push('  updated_at: string;');
        lines.push('  data: Record<string, unknown>;');
        lines.push('}');
        lines.push('');
        lines.push(`export interface I${ctxPascal}Port {`);
        lines.push(`  execute(command: string, payload: Record<string, unknown>): Promise<${ctxPascal}Record>;`);
        lines.push(`  query(filters: Record<string, unknown>, limit: number, offset: number): Promise<{ rows: ${ctxPascal}Record[]; total: number }>;`);
        lines.push(`  getById(id: string): Promise<${ctxPascal}Record | null>;`);
        lines.push('}');
        lines.push('');
        lines.push(`class ${ctxPascal}Adapter implements I${ctxPascal}Port {`);
        lines.push('  constructor(private readonly db: DbClient, private readonly telemetry: Telemetry) {}');
        lines.push('');
        lines.push(`  async execute(command: string, payload: Record<string, unknown>): Promise<${ctxPascal}Record> {`);
        lines.push(`    return this.telemetry.withSpan('${ctxSnake}', command, {}, async () => {`);
        lines.push('      const id = crypto.randomUUID();');
        lines.push('      const now = new Date().toISOString();');
        lines.push('      await this.db.execute(');
        lines.push(`        'INSERT INTO ${tableName} (id, status, created_at, updated_at, data) VALUES (:1, :2, :3, :4, :5)',`);
        lines.push("        [id, 'active', now, now, JSON.stringify(payload)],");
        lines.push('      );');
        lines.push(`      return { id, status: 'active', created_at: now, updated_at: now, data: payload };`);
        lines.push('    });');
        lines.push('  }');
        lines.push('');
        lines.push(`  async query(filters: Record<string, unknown>, limit: number, offset: number): Promise<{ rows: ${ctxPascal}Record[]; total: number }> {`);
        lines.push(`    return this.telemetry.withSpan('${ctxSnake}', 'query', {}, async () => {`);
        lines.push('      void filters;');
        lines.push(`      const countRows = await this.db.query('SELECT COUNT(*) as cnt FROM ${tableName}', []);`);
        lines.push("      const total = (countRows[0] as Record<string, number>)?.cnt ?? 0;");
        lines.push('      const dataRows = await this.db.query(');
        lines.push(`        \`SELECT * FROM ${tableName} ORDER BY created_at DESC LIMIT \${limit} OFFSET \${offset}\`,`);
        lines.push('        [],');
        lines.push('      );');
        lines.push(`      return { rows: dataRows as unknown as ${ctxPascal}Record[], total };`);
        lines.push('    });');
        lines.push('  }');
        lines.push('');
        lines.push(`  async getById(id: string): Promise<${ctxPascal}Record | null> {`);
        lines.push(`    const rows = await this.db.query('SELECT * FROM ${tableName} WHERE id = :1', [id]);`);
        lines.push(`    return (rows[0] as ${ctxPascal}Record) ?? null;`);
        lines.push('  }');
        lines.push('}');
        lines.push('');
    }
    // Dependencies interface
    lines.push('// ============================================================================');
    lines.push('// Dependency Container');
    lines.push('// ============================================================================');
    lines.push('');
    lines.push('export interface Dependencies {');
    for (const ctx of contexts) {
        lines.push(`  readonly ${camelCase(ctx.name)}: I${pascalCase(ctx.name)}Port;`);
    }
    lines.push('  readonly audit: IAuditPort;');
    lines.push('  readonly erp: ErpClient;');
    lines.push('}');
    lines.push('');
    // Factory
    lines.push('export function createDependencies(db: DbClient, erp: ErpClient): Dependencies {');
    lines.push('  const telemetry: Telemetry = new ConsoleTelemetry();');
    lines.push('  return {');
    for (const ctx of contexts) {
        lines.push(`    ${camelCase(ctx.name)}: new ${pascalCase(ctx.name)}Adapter(db, telemetry),`);
    }
    lines.push('    audit: new AuditAdapter(db),');
    lines.push('    erp,');
    lines.push('  };');
    lines.push('}');
    lines.push('');
    return lines.join('\n');
}
/**
 * Generate TypeScript routes.ts with real domain logic wired through port adapters.
 * Each handler validates with Zod, delegates to the appropriate adapter, emits
 * audit events, and returns proper HTTP status codes.
 */
/** Map ERP system to client class name and env var prefix. */
function resolveErpClientMeta(erpSystem) {
    const n = erpSystem.toLowerCase();
    if (n.includes('netsuite'))
        return { className: 'NetSuiteClient', envPrefix: 'NETSUITE', baseUrlDefault: 'https://netsuite.example.com' };
    if (n.includes('sap'))
        return { className: 'SAPClient', envPrefix: 'SAP', baseUrlDefault: 'https://sap.example.com' };
    if (n.includes('dynamics'))
        return { className: 'DynamicsClient', envPrefix: 'DYNAMICS', baseUrlDefault: 'https://dynamics.example.com' };
    if (n.includes('deltek') || n.includes('costpoint'))
        return { className: 'DeltekClient', envPrefix: 'DELTEK', baseUrlDefault: 'https://costpoint.example.com' };
    if (n.includes('guidewire'))
        return { className: 'GuidewireClient', envPrefix: 'GUIDEWIRE', baseUrlDefault: 'https://guidewire.example.com' };
    if (n.includes('epicor'))
        return { className: 'EpicorClient', envPrefix: 'EPICOR', baseUrlDefault: 'https://epicor.example.com' };
    if (n.includes('infor'))
        return { className: 'InforClient', envPrefix: 'INFOR', baseUrlDefault: 'https://infor.example.com' };
    if (n.includes('workday'))
        return { className: 'WorkdayClient', envPrefix: 'WORKDAY', baseUrlDefault: 'https://workday.example.com' };
    if (n.includes('ifs'))
        return { className: 'IFSClient', envPrefix: 'IFS', baseUrlDefault: 'https://ifs.example.com' };
    if (n.includes('sage') || n.includes('intacct'))
        return { className: 'SageClient', envPrefix: 'SAGE', baseUrlDefault: 'https://intacct.example.com' };
    if (n.includes('oracle'))
        return { className: 'OracleERPClient', envPrefix: 'ORACLE_ERP', baseUrlDefault: 'https://oracle-erp.example.com' };
    return { className: 'ERPClient', envPrefix: 'ERP', baseUrlDefault: 'https://erp.example.com' };
}
function tsRoutes(erpSystem, contexts) {
    const erpMeta = resolveErpClientMeta(erpSystem);
    // Collect all schema import names from contexts
    const schemaImports = [];
    for (const ctx of contexts) {
        for (const cmd of ctx.commands) {
            schemaImports.push(`${pascalCase(cmd.name)}Schema`);
        }
        for (const query of ctx.queries) {
            schemaImports.push(`${pascalCase(query.name)}Schema`);
        }
    }
    const lines = [
        GENERATED_HEADER_TS,
        `import { Hono } from 'hono';`,
        `import * as crypto from 'node:crypto';`,
        `import { correlationMiddleware, iamMiddleware, loggingMiddleware } from './middleware.js';`,
        `import { healthRouter } from './health.js';`,
        `import { createDependencies } from './dependencies.js';`,
        `import type { Dependencies } from './dependencies.js';`,
        `import type { DbClient, ErpClient } from '../infra/clients.js';`,
        `import { ${erpMeta.className} } from '../erp-client/client.js';`,
        `import type { GovernanceConfig } from '../erp-client/types.js';`,
        schemaImports.length > 0
            ? `import {\n${schemaImports.map(s => `  ${s},`).join('\n')}\n} from './schemas.js';`
            : `// No schemas generated`,
        '',
        '// ============================================================================',
        '// Error Response Helpers',
        '// ============================================================================',
        '',
        'interface ApiError {',
        '  readonly error: {',
        '    readonly code: string;',
        '    readonly message: string;',
        '    readonly details?: unknown;',
        '  };',
        '  readonly correlationId: string;',
        '}',
        '',
        'function validationError(correlationId: string, issues: unknown): ApiError {',
        '  return { error: { code: \'VALIDATION_ERROR\', message: \'Request validation failed\', details: issues }, correlationId };',
        '}',
        '',
        'function serverError(correlationId: string, message: string): ApiError {',
        '  return { error: { code: \'INTERNAL_ERROR\', message }, correlationId };',
        '}',
        '',
        '// ============================================================================',
        '// DI Bootstrap',
        '// ============================================================================',
        '',
        'const dbClient: DbClient = {',
        '  async query(sql: string, params: unknown[]): Promise<unknown[]> {',
        '    void sql; void params;',
        '    return [];',
        '  },',
        '  async execute(sql: string, params: unknown[]): Promise<void> {',
        '    void sql; void params;',
        '  },',
        '};',
        '',
        'const erpClient: ErpClient = {',
        '  async invoke(operation: string, data: unknown): Promise<unknown> {',
        '    void operation; void data;',
        '    return {};',
        '  },',
        '};',
        '',
        'const deps: Dependencies = createDependencies(dbClient, erpClient);',
        '',
        `// ${erpMeta.className} ACL client with governance enforcement`,
        'const governanceConfig: GovernanceConfig = {',
        '  read_only: (process.env.ERP_READ_ONLY ?? \'true\') === \'true\',',
        '  audit_enabled: true,',
        '  pii_redaction: true,',
        '  max_tokens_per_request: 4096,',
        '};',
        '',
        `const erpClientInstance = new ${erpMeta.className}({`,
        `  baseUrl: process.env.${erpMeta.envPrefix}_BASE_URL ?? '${erpMeta.baseUrlDefault}',`,
        `  serviceAccountEmail: process.env.${erpMeta.envPrefix}_SA_EMAIL ?? '${erpMeta.envPrefix.toLowerCase()}-sa@project.iam.gserviceaccount.com',`,
        '  governance: governanceConfig,',
        '  audit: {',
        '    async log(entityType, entityId, action, actor, payload) {',
        '      await deps.audit.log(entityType, entityId, action, actor, payload);',
        '    },',
        '  },',
        '});',
        '',
        '// ============================================================================',
        '// App & Middleware',
        '// ============================================================================',
        '',
        'const app = new Hono();',
        '',
        'app.use(\'*\', correlationMiddleware);',
        'app.use(\'*\', iamMiddleware);',
        'app.use(\'*\', loggingMiddleware);',
        '',
        'app.route(\'/health\', healthRouter);',
        '',
    ];
    let routeCount = 0;
    // Generate one POST per command, one GET per query, grouped by bounded context
    for (const ctx of contexts) {
        const ctxSlug = slugify(ctx.name.replace(/Context$/i, ''));
        const ctxPascal = pascalCase(ctx.name.replace(/Context$/i, ''));
        lines.push(`// ============================================================================`, `// Bounded Context: ${ctxPascal}`, `// ============================================================================`, '');
        // POST handlers for commands
        for (const cmd of ctx.commands) {
            const cmdSlug = slugify(cmd.name);
            const schemaName = `${pascalCase(cmd.name)}Schema`;
            const portName = `${camelCase(ctx.name.replace(/Context$/i, ''))}Port`;
            lines.push(`app.post('/api/v1/${ctxSlug}/${cmdSlug}', async (c) => {`, '  const correlationId = c.get(\'correlationId\') as string;', '  const startTime = Date.now();', '  try {', '    const body = await c.req.json();', `    const parsed = ${schemaName}.safeParse(body);`, '    if (!parsed.success) {', '      return c.json(validationError(correlationId, parsed.error.issues), 400);', '    }', `    const result = await deps.${portName}.execute('${cmd.name}', parsed.data);`, `    await deps.audit.log('${ctxSlug}', result?.id ?? correlationId, '${cmdSlug}', correlationId, parsed.data);`, '    return c.json({', '      correlationId,', '      status: \'completed\',', '      data: result,', '      metadata: { response_time_ms: Date.now() - startTime },', '    }, 201);', '  } catch (err) {', '    const message = err instanceof Error ? err.message : \'Unknown error\';', '    return c.json(serverError(correlationId, message), 500);', '  }', '});', '');
            routeCount++;
        }
        // GET handlers for queries
        for (const query of ctx.queries) {
            const querySlug = slugify(query.name);
            const schemaName = `${pascalCase(query.name)}Schema`;
            const portName = `${camelCase(ctx.name.replace(/Context$/i, ''))}Port`;
            lines.push(`app.get('/api/v1/${ctxSlug}/${querySlug}', async (c) => {`, '  const correlationId = c.get(\'correlationId\') as string;', '  const startTime = Date.now();', '  try {', '    const params = c.req.query();', `    const parsed = ${schemaName}.safeParse(params);`, '    if (!parsed.success) {', '      return c.json(validationError(correlationId, parsed.error.issues), 400);', '    }', `    const result = await deps.${portName}.query('${query.name}', parsed.data);`, '    return c.json({', '      correlationId,', '      status: \'ok\',', '      data: result.rows,', '      pagination: { limit: parsed.data.limit ?? 50, offset: parsed.data.offset ?? 0, total: result.total },', '      metadata: { response_time_ms: Date.now() - startTime },', '    }, 200);', '  } catch (err) {', '    const message = err instanceof Error ? err.message : \'Unknown error\';', '    return c.json(serverError(correlationId, message), 500);', '  }', '});', '');
            routeCount++;
        }
    }
    // ERP sync status endpoint (reusable across all ERP types)
    lines.push('// ============================================================================', '// GET /api/v1/erp/sync-status — ERP sync queue and circuit breaker', '// ============================================================================', '', 'app.get(\'/api/v1/erp/sync-status\', async (c) => {', '  const correlationId = c.get(\'correlationId\') as string;', '  const startTime = Date.now();', '  try {', '    const status = erpClientInstance.getSyncStatus();', '    return c.json({', '      correlationId,', '      status: \'ok\',', '      data: status,', '      metadata: { response_time_ms: Date.now() - startTime },', '    }, 200);', '  } catch (err) {', '    const message = err instanceof Error ? err.message : \'Unknown error\';', '    return c.json(serverError(correlationId, message), 500);', '  }', '});', '', 'export { app, erpClientInstance };', '');
    routeCount++; // sync-status
    return { content: lines.join('\n'), routeCount };
}
// ============================================================================
// TypeScript Middleware, Health (unchanged)
// ============================================================================
function tsMiddleware() {
    return [
        GENERATED_HEADER_TS,
        `import type { MiddlewareHandler } from 'hono';`,
        `import { createMiddleware } from 'hono/factory';`,
        `import * as crypto from 'node:crypto';`,
        `import * as jwt from 'jsonwebtoken';`,
        `import jwksClient from 'jwks-rsa';`,
        '',
        '// ---------------------------------------------------------------------------',
        '// Correlation ID middleware',
        '// ---------------------------------------------------------------------------',
        '',
        'export const correlationMiddleware: MiddlewareHandler = createMiddleware(async (c, next) => {',
        '  const correlationId = c.req.header(\'X-Correlation-ID\') ?? crypto.randomUUID();',
        '  c.set(\'correlationId\', correlationId);',
        '  c.header(\'X-Correlation-ID\', correlationId);',
        '  await next();',
        '});',
        '',
        '// ---------------------------------------------------------------------------',
        '// IAM token validation middleware (JWKS-backed JWT verification)',
        '// ---------------------------------------------------------------------------',
        '',
        'const JWKS_URI = process.env.IAM_JWKS_URI ?? \'https://www.googleapis.com/oauth2/v3/certs\';',
        'const IAM_AUDIENCE = process.env.IAM_AUDIENCE ?? \'\';',
        'const IAM_ISSUERS = (process.env.IAM_ISSUERS ?? \'https://accounts.google.com\').split(\',\').map(s => s.trim());',
        '',
        'const jwksRsa = jwksClient({',
        '  jwksUri: JWKS_URI,',
        '  cache: true,',
        '  cacheMaxAge: 600_000,',
        '  rateLimit: true,',
        '  jwksRequestsPerMinute: 10,',
        '});',
        '',
        'function getSigningKey(kid: string): Promise<string> {',
        '  return new Promise((resolve, reject) => {',
        '    jwksRsa.getSigningKey(kid, (err, key) => {',
        '      if (err || !key) return reject(err ?? new Error(\'No signing key found\'));',
        '      resolve(key.getPublicKey());',
        '    });',
        '  });',
        '}',
        '',
        'export const iamMiddleware: MiddlewareHandler = createMiddleware(async (c, next) => {',
        '  const path = new URL(c.req.url).pathname;',
        '  if (path === \'/health/healthz\' || path === \'/health/readyz\') {',
        '    await next();',
        '    return;',
        '  }',
        '',
        '  const authHeader = c.req.header(\'Authorization\');',
        '  if (!authHeader?.startsWith(\'Bearer \')) {',
        '    return c.json({ error: { code: \'UNAUTHORIZED\', message: \'Missing Bearer token\' } }, 401);',
        '  }',
        '',
        '  const token = authHeader.slice(7);',
        '  if (!token) {',
        '    return c.json({ error: { code: \'UNAUTHORIZED\', message: \'Empty token\' } }, 401);',
        '  }',
        '',
        '  try {',
        '    const decoded = jwt.decode(token, { complete: true });',
        '    if (!decoded || typeof decoded === \'string\' || !decoded.header.kid) {',
        '      return c.json({ error: { code: \'UNAUTHORIZED\', message: \'Malformed token: missing kid\' } }, 401);',
        '    }',
        '',
        '    const signingKey = await getSigningKey(decoded.header.kid);',
        '',
        '    const verifyOptions: jwt.VerifyOptions = {',
        '      algorithms: [\'RS256\', \'RS384\', \'RS512\', \'ES256\', \'ES384\'],',
        '    };',
        '    if (IAM_ISSUERS.length > 0 && IAM_ISSUERS[0] !== \'\') {',
        '      verifyOptions.issuer = IAM_ISSUERS;',
        '    }',
        '    if (IAM_AUDIENCE) {',
        '      verifyOptions.audience = IAM_AUDIENCE;',
        '    }',
        '',
        '    const payload = jwt.verify(token, signingKey, verifyOptions);',
        '    c.set(\'iamClaims\', payload);',
        '  } catch (err) {',
        '    const message = err instanceof Error ? err.message : \'Token verification failed\';',
        '    return c.json({ error: { code: \'UNAUTHORIZED\', message } }, 401);',
        '  }',
        '',
        '  await next();',
        '});',
        '',
        '// ---------------------------------------------------------------------------',
        '// Structured JSON request/response logging middleware',
        '// ---------------------------------------------------------------------------',
        '',
        'export const loggingMiddleware: MiddlewareHandler = createMiddleware(async (c, next) => {',
        '  const startTime = Date.now();',
        '  const correlationId = c.get(\'correlationId\') as string | undefined;',
        '',
        '  process.stdout.write(',
        '    JSON.stringify({',
        '      severity: \'INFO\',',
        '      message: \'request received\',',
        '      httpRequest: {',
        '        method: c.req.method,',
        '        url: c.req.url,',
        '        userAgent: c.req.header(\'user-agent\'),',
        '      },',
        '      correlationId,',
        '      timestamp: new Date().toISOString(),',
        '    }) + \'\\n\',',
        '  );',
        '',
        '  await next();',
        '',
        '  const durationMs = Date.now() - startTime;',
        '  process.stdout.write(',
        '    JSON.stringify({',
        '      severity: \'INFO\',',
        '      message: \'request completed\',',
        '      httpRequest: {',
        '        method: c.req.method,',
        '        url: c.req.url,',
        '        status: c.res.status,',
        '        latency: `${durationMs}ms`,',
        '      },',
        '      correlationId,',
        '      timestamp: new Date().toISOString(),',
        '    }) + \'\\n\',',
        '  );',
        '});',
        '',
    ].join('\n');
}
function tsHealth() {
    return [
        GENERATED_HEADER_TS,
        `import { Hono } from 'hono';`,
        '',
        'export const healthRouter = new Hono();',
        '',
        'healthRouter.get(\'/healthz\', (c) => {',
        '  return c.json({ status: \'ok\' }, 200);',
        '});',
        '',
        'healthRouter.get(\'/readyz\', (c) => {',
        '  return c.json({ status: \'ready\' }, 200);',
        '});',
        '',
    ].join('\n');
}
// ============================================================================
// Test File Generators
// ============================================================================
function tsHealthTest() {
    return [
        GENERATED_HEADER_TS,
        `import { describe, it, expect } from 'vitest';`,
        `import { Hono } from 'hono';`,
        `import { healthRouter } from '../server/health.js';`,
        '',
        'describe(\'Health Endpoints\', () => {',
        '  const app = new Hono().route(\'/health\', healthRouter);',
        '',
        '  it(\'GET /health/healthz returns 200\', async () => {',
        '    const res = await app.request(\'/health/healthz\');',
        '    expect(res.status).toBe(200);',
        '    const body = await res.json();',
        '    expect(body.status).toBe(\'ok\');',
        '  });',
        '',
        '  it(\'GET /health/readyz returns 200\', async () => {',
        '    const res = await app.request(\'/health/readyz\');',
        '    expect(res.status).toBe(200);',
        '    const body = await res.json();',
        '    expect(body.status).toBe(\'ready\');',
        '  });',
        '});',
        '',
    ].join('\n');
}
function tsContextTest(ctx) {
    const ctxPascal = pascalCase(ctx.name);
    const ctxLabel = ctx.name.replace(/-/g, ' ');
    const lines = [
        GENERATED_HEADER_TS,
        `import { describe, it, expect } from 'vitest';`,
        `import type { ${ctxPascal}Record, I${ctxPascal}Port } from '../server/dependencies.js';`,
        '',
        `// Mock adapter for ${ctxLabel}`,
        `class Mock${ctxPascal}Adapter implements I${ctxPascal}Port {`,
        '  private store: Map<string, Record<string, unknown>> = new Map();',
        '',
        `  async execute(command: string, payload: Record<string, unknown>): Promise<${ctxPascal}Record> {`,
        '    const id = crypto.randomUUID();',
        '    const now = new Date().toISOString();',
        `    const record: ${ctxPascal}Record = { id, status: 'active', created_at: now, updated_at: now, data: payload };`,
        '    this.store.set(id, record as unknown as Record<string, unknown>);',
        '    return record;',
        '  }',
        '',
        `  async query(_filters: Record<string, unknown>, limit: number, offset: number): Promise<{ rows: ${ctxPascal}Record[]; total: number }> {`,
        '    const all = [...this.store.values()];',
        `    return { rows: all.slice(offset, offset + limit) as unknown as ${ctxPascal}Record[], total: all.length };`,
        '  }',
        '',
        `  async getById(id: string): Promise<${ctxPascal}Record | null> {`,
        `    return (this.store.get(id) as unknown as ${ctxPascal}Record) ?? null;`,
        '  }',
        '}',
        '',
        `describe('${ctxLabel} Port', () => {`,
        `  const adapter = new Mock${ctxPascal}Adapter();`,
        '',
        `  it('execute() creates a record with active status', async () => {`,
        `    const result = await adapter.execute('create', { name: 'test-entity' });`,
        `    expect(result.id).toBeDefined();`,
        `    expect(result.status).toBe('active');`,
        `    expect(result.data.name).toBe('test-entity');`,
        `    expect(result.created_at).toBeDefined();`,
        '  });',
        '',
        `  it('getById() retrieves a created record', async () => {`,
        `    const created = await adapter.execute('create', { value: 42 });`,
        `    const found = await adapter.getById(created.id);`,
        `    expect(found).not.toBeNull();`,
        `    expect(found!.id).toBe(created.id);`,
        `    expect(found!.data.value).toBe(42);`,
        '  });',
        '',
        `  it('getById() returns null for non-existent id', async () => {`,
        `    const found = await adapter.getById('non-existent-id');`,
        `    expect(found).toBeNull();`,
        '  });',
        '',
        `  it('query() returns paginated results', async () => {`,
        `    const mockAdapter = new Mock${ctxPascal}Adapter();`,
        `    await mockAdapter.execute('create', { idx: 1 });`,
        `    await mockAdapter.execute('create', { idx: 2 });`,
        `    await mockAdapter.execute('create', { idx: 3 });`,
        `    const result = await mockAdapter.query({}, 2, 0);`,
        `    expect(result.total).toBe(3);`,
        `    expect(result.rows).toHaveLength(2);`,
        '  });',
        '});',
        '',
    ];
    return lines.join('\n');
}
// ============================================================================
// Rust Mock Data Helpers (unchanged — Rust routes remain mocked)
// ============================================================================
function buildRustMockCommandJson(ctxSlug, opSlug) {
    return `    Ok(Json(json!({
        "status": "completed",
        "command": "${opSlug}",
        "bounded_context": "${ctxSlug}",
        "data": {
            "aggregate_id": uuid::Uuid::new_v4().to_string(),
            "processed_at": chrono::Utc::now().to_rfc3339(),
            "processing_time_ms": start.elapsed().as_millis() as u64
        },
        "metadata": body
    })))`;
}
function buildRustMockQueryJson(ctxSlug, opSlug) {
    return `    let page: usize = params.get("page").and_then(|p| p.parse().ok()).unwrap_or(1);
    Ok(Json(json!({
        "status": "ok",
        "query": "${opSlug}",
        "bounded_context": "${ctxSlug}",
        "data": [],
        "pagination": { "page": page, "per_page": 20, "total": 0 }
    })))`;
}
// ============================================================================
// Rust / Axum Generation (unchanged)
// ============================================================================
function rustMain() {
    return [
        GENERATED_HEADER_RS,
        'mod health;',
        'mod middleware;',
        'mod routes;',
        '',
        'use axum::Router;',
        'use std::net::SocketAddr;',
        '',
        '#[tokio::main]',
        'async fn main() {',
        '    let port: u16 = std::env::var("PORT")',
        '        .unwrap_or_else(|_| "8080".to_string())',
        '        .parse()',
        '        .expect("PORT must be a valid port number");',
        '',
        '    let app = routes::create_router();',
        '',
        '    let addr = SocketAddr::from(([0, 0, 0, 0], port));',
        '    println!("{}", serde_json::json!({',
        '        "severity": "INFO",',
        '        "message": "HTTP server started",',
        '        "port": port,',
        '        "timestamp": chrono::Utc::now().to_rfc3339()',
        '    }));',
        '',
        '    axum::Server::bind(&addr)',
        '        .serve(app.into_make_service())',
        '        .await',
        '        .unwrap();',
        '}',
        '',
    ].join('\n');
}
function rustRoutes(contexts) {
    const lines = [
        GENERATED_HEADER_RS,
        'use axum::{',
        '    extract::Query,',
        '    http::StatusCode,',
        '    response::Json,',
        '    routing::{get, post},',
        '    Router,',
        '};',
        'use serde_json::{json, Value};',
        'use std::collections::HashMap;',
        'use tokio;',
        '',
        'pub fn create_router() -> Router {',
        '    Router::new()',
        '        .route("/health/healthz", get(crate::health::healthz))',
        '        .route("/health/readyz", get(crate::health::readyz))',
    ];
    let routeCount = 0;
    for (const ctx of contexts) {
        const ctxSlug = slugify(ctx.name.replace(/Context$/i, ''));
        for (const cmd of ctx.commands) {
            const cmdSlug = slugify(cmd.name);
            const fnName = snakeCase(cmd.name);
            lines.push(`        .route("/api/${ctxSlug}/${cmdSlug}", post(handle_${fnName}))`);
            routeCount++;
        }
        for (const query of ctx.queries) {
            const querySlug = slugify(query.name);
            const fnName = snakeCase(query.name);
            lines.push(`        .route("/api/${ctxSlug}/${querySlug}", get(handle_${fnName}))`);
            routeCount++;
        }
    }
    lines.push('}');
    lines.push('');
    for (const ctx of contexts) {
        const ctxSlug = slugify(ctx.name.replace(/Context$/i, ''));
        lines.push(`// Bounded context: ${ctx.name}`);
        for (const cmd of ctx.commands) {
            const fnName = snakeCase(cmd.name);
            const cmdSlug = slugify(cmd.name);
            lines.push(`async fn handle_${fnName}(`);
            lines.push(`    Json(body): Json<Value>,`);
            lines.push(`) -> Result<Json<Value>, StatusCode> {`);
            lines.push(`    let start = std::time::Instant::now();`);
            lines.push(buildRustMockCommandJson(ctxSlug, cmdSlug));
            lines.push(`}`);
            lines.push('');
        }
        for (const query of ctx.queries) {
            const fnName = snakeCase(query.name);
            const querySlug = slugify(query.name);
            lines.push(`async fn handle_${fnName}(`);
            lines.push(`    Query(params): Query<HashMap<String, String>>,`);
            lines.push(`) -> Result<Json<Value>, StatusCode> {`);
            lines.push(buildRustMockQueryJson(ctxSlug, querySlug));
            lines.push(`}`);
            lines.push('');
        }
    }
    return { content: lines.join('\n'), routeCount };
}
function rustMiddleware() {
    return [
        GENERATED_HEADER_RS,
        'use axum::{',
        '    body::Body,',
        '    http::{Request, Response},',
        '    middleware::Next,',
        '    response::IntoResponse,',
        '};',
        'use uuid::Uuid;',
        '',
        'pub async fn correlation_middleware(',
        '    mut req: Request<Body>,',
        '    next: Next<Body>,',
        ') -> impl IntoResponse {',
        '    let correlation_id = req',
        '        .headers()',
        '        .get("x-correlation-id")',
        '        .and_then(|v| v.to_str().ok())',
        '        .map(|s| s.to_string())',
        '        .unwrap_or_else(|| Uuid::new_v4().to_string());',
        '',
        '    req.extensions_mut().insert(correlation_id.clone());',
        '',
        '    let mut response = next.run(req).await;',
        '    response.headers_mut().insert(',
        '        "x-correlation-id",',
        '        correlation_id.parse().unwrap(),',
        '    );',
        '    response',
        '}',
        '',
        'fn jwks_uri() -> String {',
        '    std::env::var("IAM_JWKS_URI")',
        '        .unwrap_or_else(|_| "https://www.googleapis.com/oauth2/v3/certs".into())',
        '}',
        '',
        'fn iam_audience() -> Option<String> {',
        '    std::env::var("IAM_AUDIENCE").ok().filter(|s| !s.is_empty())',
        '}',
        '',
        'fn iam_issuers() -> Vec<String> {',
        '    std::env::var("IAM_ISSUERS")',
        '        .unwrap_or_else(|_| "https://accounts.google.com".into())',
        '        .split(\',\')',
        '        .map(|s| s.trim().to_string())',
        '        .filter(|s| !s.is_empty())',
        '        .collect()',
        '}',
        '',
        'pub async fn iam_middleware(',
        '    req: Request<Body>,',
        '    next: Next<Body>,',
        ') -> impl IntoResponse {',
        '    let path = req.uri().path().to_string();',
        '    if path == "/health/healthz" || path == "/health/readyz" {',
        '        return next.run(req).await.into_response();',
        '    }',
        '',
        '    let auth_header = req',
        '        .headers()',
        '        .get("authorization")',
        '        .and_then(|v| v.to_str().ok())',
        '        .map(|s| s.to_string());',
        '',
        '    match auth_header {',
        '        Some(header) if header.starts_with("Bearer ") => {',
        '            let token = &header[7..];',
        '            let header = match jsonwebtoken::decode_header(token) {',
        '                Ok(h) => h,',
        '                Err(_) => return unauthorized("Malformed token header"),',
        '            };',
        '            let kid = match header.kid {',
        '                Some(k) => k,',
        '                None => return unauthorized("Token missing kid claim"),',
        '            };',
        '',
        '            let jwks_url = jwks_uri();',
        '            let jwks = match reqwest::get(&jwks_url).await {',
        '                Ok(resp) => match resp.json::<jsonwebtoken::jwk::JwkSet>().await {',
        '                    Ok(set) => set,',
        '                    Err(_) => return unauthorized("Failed to parse JWKS"),',
        '                },',
        '                Err(_) => return unauthorized("Failed to fetch JWKS"),',
        '            };',
        '',
        '            let jwk = match jwks.find(&kid) {',
        '                Some(k) => k,',
        '                None => return unauthorized("No matching key in JWKS"),',
        '            };',
        '',
        '            let decoding_key = match jsonwebtoken::DecodingKey::from_jwk(jwk) {',
        '                Ok(k) => k,',
        '                Err(_) => return unauthorized("Invalid JWK"),',
        '            };',
        '',
        '            let mut validation = jsonwebtoken::Validation::new(header.alg);',
        '            let issuers = iam_issuers();',
        '            if !issuers.is_empty() {',
        '                validation.set_issuer(&issuers);',
        '            }',
        '            if let Some(aud) = iam_audience() {',
        '                validation.set_audience(&[aud]);',
        '            } else {',
        '                validation.validate_aud = false;',
        '            }',
        '',
        '            match jsonwebtoken::decode::<serde_json::Value>(token, &decoding_key, &validation) {',
        '                Ok(_) => next.run(req).await.into_response(),',
        '                Err(e) => unauthorized(&format!("Token verification failed: {e}")),',
        '            }',
        '        }',
        '        _ => unauthorized("Missing Bearer token"),',
        '    }',
        '}',
        '',
        'fn unauthorized(msg: &str) -> axum::http::Response<axum::body::Body> {',
        '    axum::http::Response::builder()',
        '        .status(401)',
        '        .header("content-type", "application/json")',
        '        .body(axum::body::Body::from(',
        '            serde_json::json!({"error":{"code":"UNAUTHORIZED","message":msg}}).to_string()',
        '        ))',
        '        .unwrap()',
        '}',
        '',
        'pub async fn logging_middleware(',
        '    req: Request<Body>,',
        '    next: Next<Body>,',
        ') -> impl IntoResponse {',
        '    let method = req.method().clone();',
        '    let uri = req.uri().clone();',
        '    let start = std::time::Instant::now();',
        '',
        '    println!("{}", serde_json::json!({',
        '        "severity": "INFO",',
        '        "message": "request received",',
        '        "httpRequest": { "method": method.as_str(), "url": uri.to_string() },',
        '        "timestamp": chrono::Utc::now().to_rfc3339()',
        '    }));',
        '',
        '    let response = next.run(req).await;',
        '    let duration_ms = start.elapsed().as_millis();',
        '',
        '    println!("{}", serde_json::json!({',
        '        "severity": "INFO",',
        '        "message": "request completed",',
        '        "httpRequest": {',
        '            "method": method.as_str(),',
        '            "url": uri.to_string(),',
        '            "status": response.status().as_u16(),',
        '            "latency": format!("{}ms", duration_ms)',
        '        },',
        '        "timestamp": chrono::Utc::now().to_rfc3339()',
        '    }));',
        '',
        '    response',
        '}',
        '',
    ].join('\n');
}
function rustHealth() {
    return [
        GENERATED_HEADER_RS,
        'use axum::{http::StatusCode, response::Json};',
        'use serde_json::{json, Value};',
        '',
        'pub async fn healthz() -> (StatusCode, Json<Value>) {',
        '    (StatusCode::OK, Json(json!({ "status": "ok" })))',
        '}',
        '',
        'pub async fn readyz() -> (StatusCode, Json<Value>) {',
        '    (StatusCode::OK, Json(json!({ "status": "ready" })))',
        '}',
        '',
    ].join('\n');
}
// ============================================================================
// File Writing
// ============================================================================
function writeServerFile(absPath, content) {
    const dir = absPath.substring(0, absPath.lastIndexOf('/'));
    mkdirSync(dir, { recursive: true, mode: DIR_MODE });
    try {
        writeFileSync(absPath, content, { encoding: 'utf-8', mode: FILE_MODE });
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        throw new Error(`ECLI-P4-051: Failed to write server file ${absPath}: ${message}`);
    }
}
// ============================================================================
// Public: Pipeline Entry Point
// ============================================================================
export function generateHttpServer(context) {
    const startTime = Date.now();
    const rootSpan = createSpan('http-server-generation', 'generate-http-server', {
        phase4Dir: context.phase4Dir,
        traceId: context.traceId,
    });
    try {
        // Verify ERP target resolution completed
        const erpStage = context.stageOutputs.get('erp-target-resolution');
        if (!erpStage || erpStage.status !== 'completed') {
            throw new Error('ECLI-P4-050: ERP target resolution not available on context. ' +
                'Ensure the erp-target-resolution stage completed successfully before running http-server-generation.');
        }
        const erpResolution = erpStage.data;
        // Resolve implementation language and bounded contexts
        const language = context.phase3Artifacts.implementationLanguage;
        // Non-TS/Rust languages handled by Ruflo swarm
        if (!hasTemplateSupport(language)) {
            const span = endSpan(rootSpan, 'ok', { delegated: true, language });
            emitSpan(span);
            return {
                stage: 'http-server-generation',
                status: 'completed',
                data: { framework: 'hono', files: [], routeCount: 0 },
                artifacts: [],
                timing: Date.now() - startTime,
                summary: `HTTP server for ${language} delegated to Ruflo swarm.`,
            };
        }
        const domainModel = context.phase3Artifacts.phase2Artifacts.domainModel;
        // Flatten commands/queries/entities/VOs from aggregates to context level for route generation.
        const contexts = domainModel.contexts.map((ctx) => {
            const aggregates = ctx['aggregates'] ?? [];
            const commands = [];
            const queries = [];
            const entities = [];
            const valueObjects = [];
            for (const agg of aggregates) {
                const aggCommands = agg['commands'] ?? [];
                const aggQueries = agg['queries'] ?? [];
                const aggEntities = agg['entities'] ?? [];
                const aggVOs = agg['valueObjects'] ?? [];
                for (const cmd of aggCommands) {
                    commands.push({
                        name: cmd['name'],
                        description: cmd['description'] ?? '',
                        input: cmd['input'] ?? [],
                        preconditions: cmd['preconditions'] ?? [],
                        postconditions: cmd['postconditions'] ?? [],
                    });
                }
                for (const q of aggQueries) {
                    queries.push({
                        name: q['name'],
                        description: q['description'] ?? '',
                        parameters: q['parameters'] ?? [],
                        returns: q['returns'] ?? 'unknown',
                    });
                }
                for (const ent of aggEntities) {
                    entities.push({ name: ent['name'], description: ent['description'] ?? '' });
                }
                for (const vo of aggVOs) {
                    valueObjects.push({ name: vo['name'], description: vo['description'] ?? '' });
                }
            }
            const ctxCommands = ctx['commands'] ?? [];
            const ctxQueries = ctx['queries'] ?? [];
            for (const cmd of ctxCommands) {
                commands.push({
                    name: cmd['name'],
                    description: cmd['description'] ?? '',
                    input: cmd['input'] ?? [],
                    preconditions: cmd['preconditions'] ?? [],
                    postconditions: cmd['postconditions'] ?? [],
                });
            }
            for (const q of ctxQueries) {
                queries.push({
                    name: q['name'],
                    description: q['description'] ?? '',
                    parameters: q['parameters'] ?? [],
                    returns: q['returns'] ?? 'unknown',
                });
            }
            return { name: ctx['name'], commands, queries, entities, valueObjects };
        });
        const isTs = language === 'typescript';
        const framework = isTs ? 'hono' : 'axum';
        // Output directory: phase4/project/src/server/
        const serverDir = join(context.phase4Dir, 'project', 'src', 'server');
        mkdirSync(serverDir, { recursive: true, mode: DIR_MODE });
        const serverFiles = [];
        const writtenPaths = [];
        const genSpan = createSpan('http-server-generation', 'generate-files', { language, framework, contextCount: contexts.length }, rootSpan.spanId);
        if (isTs) {
            // main.ts
            const mainContent = tsMain();
            const mainPath = join(serverDir, 'main.ts');
            writeServerFile(mainPath, mainContent);
            serverFiles.push({ relativePath: 'project/src/server/main.ts', content: mainContent, kind: 'server' });
            writtenPaths.push(mainPath);
            // schemas.ts — Zod validation schemas for all domain entities
            const schemasContent = tsSchemas(contexts);
            const schemasPath = join(serverDir, 'schemas.ts');
            writeServerFile(schemasPath, schemasContent);
            serverFiles.push({ relativePath: 'project/src/server/schemas.ts', content: schemasContent, kind: 'server' });
            writtenPaths.push(schemasPath);
            // dependencies.ts — DI factory with port interfaces, adapters, and container
            const depsContent = tsDependencies(contexts);
            const depsPath = join(serverDir, 'dependencies.ts');
            writeServerFile(depsPath, depsContent);
            serverFiles.push({ relativePath: 'project/src/server/dependencies.ts', content: depsContent, kind: 'server' });
            writtenPaths.push(depsPath);
            // routes.ts — Domain logic route handlers wired to adapters
            const { content: routesContent, routeCount } = tsRoutes(erpResolution.erpSystem, contexts);
            const routesPath = join(serverDir, 'routes.ts');
            writeServerFile(routesPath, routesContent);
            serverFiles.push({ relativePath: 'project/src/server/routes.ts', content: routesContent, kind: 'server' });
            writtenPaths.push(routesPath);
            // middleware.ts
            const middlewareContent = tsMiddleware();
            const middlewarePath = join(serverDir, 'middleware.ts');
            writeServerFile(middlewarePath, middlewareContent);
            serverFiles.push({ relativePath: 'project/src/server/middleware.ts', content: middlewareContent, kind: 'server' });
            writtenPaths.push(middlewarePath);
            // health.ts
            const healthContent = tsHealth();
            const healthPath = join(serverDir, 'health.ts');
            writeServerFile(healthPath, healthContent);
            serverFiles.push({ relativePath: 'project/src/server/health.ts', content: healthContent, kind: 'server' });
            writtenPaths.push(healthPath);
            // package.json — ensure project root has build config
            const projectDir = join(context.phase4Dir, 'project');
            const pkgJsonPath = join(projectDir, 'package.json');
            // Derive package name from scenario query — not "generated-prototype"
            const pkgName = (context.scenarioQuery ?? '')
                .toLowerCase().replace(/[^a-z0-9\s-]/g, ' ').split(/\s+/)
                .filter(w => w.length > 2 && !['the', 'and', 'for', 'with', 'from', 'that', 'this', 'our', 'are', 'was', 'has', 'have', 'been', 'will', 'would', 'could', 'should', 'can', 'build', 'create', 'make', 'implement', 'develop', 'using', 'need', 'want', 'lead', 'global', 'company', 'approximately', 'employees', 'one', 'biggest', 'challenges'].includes(w))
                .slice(0, 4).join('-') || 'enterprise-service';
            const pkgJson = JSON.stringify({
                name: pkgName,
                version: '0.1.0',
                type: 'module',
                main: './dist/server/main.js',
                scripts: {
                    build: 'tsc',
                    start: 'node dist/server/main.js',
                    dev: 'tsx watch src/server/main.ts',
                    test: 'vitest run',
                    'test:watch': 'vitest',
                    lint: 'tsc --noEmit',
                },
                dependencies: {
                    hono: '^4.0.0',
                    zod: '^3.22.0',
                    jsonwebtoken: '^9.0.0',
                    'jwks-rsa': '^3.1.0',
                },
                devDependencies: {
                    '@types/node': '^20.0.0',
                    '@types/jsonwebtoken': '^9.0.0',
                    typescript: '^5.4.0',
                    tsx: '^4.0.0',
                    vitest: '^1.6.0',
                },
            }, null, 2) + '\n';
            writeServerFile(pkgJsonPath, pkgJson);
            serverFiles.push({ relativePath: 'project/package.json', content: pkgJson, kind: 'server' });
            writtenPaths.push(pkgJsonPath);
            // tsconfig.json
            const tsconfigPath = join(projectDir, 'tsconfig.json');
            const tsconfig = JSON.stringify({
                compilerOptions: {
                    target: 'ES2022',
                    module: 'NodeNext',
                    moduleResolution: 'NodeNext',
                    strict: true,
                    outDir: 'dist',
                    rootDir: 'src',
                    declaration: true,
                    esModuleInterop: true,
                    skipLibCheck: true,
                    forceConsistentCasingInFileNames: true,
                    resolveJsonModule: true,
                },
                include: ['src'],
                exclude: ['node_modules', 'dist'],
            }, null, 2) + '\n';
            writeServerFile(tsconfigPath, tsconfig);
            serverFiles.push({ relativePath: 'project/tsconfig.json', content: tsconfig, kind: 'server' });
            writtenPaths.push(tsconfigPath);
            // vitest.config.ts
            const vitestPath = join(projectDir, 'vitest.config.ts');
            const vitestContent = [
                GENERATED_HEADER_TS,
                `import { defineConfig } from 'vitest/config';`,
                '',
                'export default defineConfig({',
                '  test: {',
                '    globals: true,',
                '    environment: \'node\',',
                "    include: ['src/**/*.test.ts'],",
                '  },',
                '});',
                '',
            ].join('\n');
            writeServerFile(vitestPath, vitestContent);
            serverFiles.push({ relativePath: 'project/vitest.config.ts', content: vitestContent, kind: 'server' });
            writtenPaths.push(vitestPath);
            // Test files — one per bounded context + health + middleware
            const testDir = join(serverDir.replace('/server', ''), '__tests__');
            mkdirSync(testDir, { recursive: true, mode: DIR_MODE });
            // Health endpoint test
            const healthTestContent = tsHealthTest();
            const healthTestPath = join(testDir, 'health.test.ts');
            writeServerFile(healthTestPath, healthTestContent);
            serverFiles.push({ relativePath: 'project/src/__tests__/health.test.ts', content: healthTestContent, kind: 'server' });
            writtenPaths.push(healthTestPath);
            // Per-context integration tests
            for (const ctx of contexts) {
                const ctxSlug = snakeCase(ctx.name);
                const testContent = tsContextTest(ctx);
                const testPath = join(testDir, `${ctxSlug}.test.ts`);
                writeServerFile(testPath, testContent);
                serverFiles.push({ relativePath: `project/src/__tests__/${ctxSlug}.test.ts`, content: testContent, kind: 'server' });
                writtenPaths.push(testPath);
            }
            const totalFiles = 8 + 1 + 1 + contexts.length; // server files + vitest + health test + context tests
            const finalGenSpan = endSpan(genSpan, 'ok', { filesGenerated: totalFiles, routeCount });
            emitSpan(finalGenSpan);
            const finalRootSpan = endSpan(rootSpan, 'ok', { framework, filesGenerated: 6, routeCount });
            emitSpan(finalRootSpan);
            const timing = Date.now() - startTime;
            const output = {
                stage: 'http-server-generation',
                status: 'completed',
                data: {
                    framework,
                    files: serverFiles,
                    routeCount,
                },
                artifacts: writtenPaths,
                timing,
                summary: `Generated ${serverFiles.length} server files (${routeCount} domain routes) using ${framework}`,
            };
            context.stageOutputs.set('http-server-generation', output);
            return output;
        }
        // Rust path (unchanged)
        const rustMainContent = rustMain();
        const rustMainPath = join(serverDir, 'main.rs');
        writeServerFile(rustMainPath, rustMainContent);
        serverFiles.push({ relativePath: 'project/src/server/main.rs', content: rustMainContent, kind: 'server' });
        writtenPaths.push(rustMainPath);
        const { content: rustRoutesContent, routeCount: rustRouteCount } = rustRoutes(contexts);
        const rustRoutesPath = join(serverDir, 'routes.rs');
        writeServerFile(rustRoutesPath, rustRoutesContent);
        serverFiles.push({ relativePath: 'project/src/server/routes.rs', content: rustRoutesContent, kind: 'server' });
        writtenPaths.push(rustRoutesPath);
        const rustMiddlewareContent = rustMiddleware();
        const rustMiddlewarePath = join(serverDir, 'middleware.rs');
        writeServerFile(rustMiddlewarePath, rustMiddlewareContent);
        serverFiles.push({ relativePath: 'project/src/server/middleware.rs', content: rustMiddlewareContent, kind: 'server' });
        writtenPaths.push(rustMiddlewarePath);
        const rustHealthContent = rustHealth();
        const rustHealthPath = join(serverDir, 'health.rs');
        writeServerFile(rustHealthPath, rustHealthContent);
        serverFiles.push({ relativePath: 'project/src/server/health.rs', content: rustHealthContent, kind: 'server' });
        writtenPaths.push(rustHealthPath);
        const finalGenSpan = endSpan(genSpan, 'ok', { filesGenerated: 4, routeCount: rustRouteCount });
        emitSpan(finalGenSpan);
        const finalRootSpan = endSpan(rootSpan, 'ok', { framework, filesGenerated: 4, routeCount: rustRouteCount });
        emitSpan(finalRootSpan);
        const timing = Date.now() - startTime;
        const output = {
            stage: 'http-server-generation',
            status: 'completed',
            data: {
                framework,
                files: serverFiles,
                routeCount: rustRouteCount,
            },
            artifacts: writtenPaths,
            timing,
            summary: `Generated ${serverFiles.length} server files (${rustRouteCount} routes) using ${framework}`,
        };
        context.stageOutputs.set('http-server-generation', output);
        return output;
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        const errorSpan = endSpan(rootSpan, 'error', { error: message });
        emitSpan(errorSpan);
        const timing = Date.now() - startTime;
        const output = {
            stage: 'http-server-generation',
            status: 'failed',
            data: { error: message },
            artifacts: [],
            timing,
            summary: `HTTP server generation failed: ${message}`,
        };
        context.stageOutputs.set('http-server-generation', output);
        return output;
    }
}
//# sourceMappingURL=http-server-generator.js.map