/**
 * Service-runtime emitter — V2 Phase 3, Round 2.
 *
 * Handles `topology: 'service'` factories: providers that ship as
 * network-attached services we deploy ourselves (REST / gRPC / MCP).
 *
 * For each `import:` entry the consumer declares, this emitter produces:
 *   clients/<provider-name>.ts                — typed network client
 *   servers/<provider-name>-server.ts (cond.) — Fastify route scaffold
 *
 * The server-bind file is emitted only when `factory.v2metadata?.serverTemplate`
 * is non-null and non-empty. Managed/external providers (we don't host them)
 * leave it null; we-host providers populate it.
 *
 * Protocol dispatch (factory.v2metadata?.protocol):
 *   'rest' (default)   → fetch-based HTTP client; one POST per dotted op
 *   'grpc'             → gRPC stub init (Round 2: TODO scaffolding)
 *   'mcp'              → MCP client init (Round 2: TODO scaffolding)
 *
 * Endpoint env-var resolution:
 *   1. factory.v2metadata?.config?.endpoint?.env if present
 *   2. Convention <UPPERCASE_PROVIDER>_API_URL otherwise
 *
 * Deployment hint: realize emits a `deployments.<env>.instances.services.<provider>`
 * entry with `config: { endpoint: { env: <ENV_NAME> } }` — mentioned in result.notes
 * for the consumer's review. The actual instance emission is upstream of this
 * emitter; here we just declare the convention.
 */
// ─── Naming helpers (shared shape with library.ts; intentionally duplicated
// rather than extracted to keep each emitter file self-contained for review) ─
function parseSelectEntry(entry) {
    const dotIndex = entry.indexOf('.');
    if (dotIndex === -1)
        return { head: entry, tail: undefined };
    return { head: entry.slice(0, dotIndex), tail: entry.slice(dotIndex + 1) };
}
function toWords(fromName) {
    const normalised = fromName.replace(/^@/, '').replace(/\//g, '-');
    return normalised
        .replace(/([a-z])([A-Z])/g, '$1 $2')
        .replace(/[^a-zA-Z0-9]+/g, ' ')
        .trim()
        .split(' ')
        .filter(Boolean)
        .map((w) => w.toLowerCase());
}
function toClientName(fromName) {
    const words = toWords(fromName);
    if (words.length === 0)
        return 'unknownClient';
    return (words[0] +
        words.slice(1).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(''));
}
function toInterfaceName(fromName) {
    return toWords(fromName)
        .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
        .join('');
}
function toFileName(fromName) {
    return toWords(fromName).join('-');
}
function toEnvVarName(fromName, suffix = '_API_URL') {
    return toWords(fromName).join('_').toUpperCase() + suffix;
}
function resolveProtocol(ctx) {
    const p = ctx.factory.v2metadata?.protocol;
    if (p === 'grpc' || p === 'mcp')
        return p;
    return 'rest';
}
function resolveEndpointEnv(ctx) {
    const config = ctx.factory.v2metadata?.config;
    return config?.endpoint?.env ?? toEnvVarName(ctx.importEntry.from, '_API_URL');
}
function resolveServerTemplate(ctx) {
    return ctx.factory.v2metadata?.serverTemplate;
}
function shouldEmitServerBind(ctx) {
    const t = resolveServerTemplate(ctx);
    return typeof t === 'string' && t.length > 0;
}
// ─── REST client generator ────────────────────────────────────────────────────
function generateRestClient(ctx, clientName, interfaceName) {
    const { importEntry } = ctx;
    const selectEntries = importEntry.select ?? [];
    const envVar = resolveEndpointEnv(ctx);
    const lines = [];
    lines.push('// FACTORY-EMITTED — DO NOT EDIT (R8)');
    lines.push(`// Generated by service-runtime emitter — V2 Phase 3`);
    lines.push(`// Provider: ${importEntry.from} (protocol: rest)`);
    lines.push(`// Endpoint: process.env.${envVar} (required at runtime)`);
    lines.push('');
    // Interface
    lines.push(`export type ${interfaceName}Client = {`);
    const seenIface = new Set();
    for (const entry of selectEntries) {
        if (seenIface.has(entry))
            continue;
        seenIface.add(entry);
        const { head, tail } = parseSelectEntry(entry);
        if (tail === undefined) {
            lines.push(`  /** ${head} entity — passthrough HTTP slot. Round 3: substitute real type. */`);
            lines.push(`  ${head}(...args: unknown[]): Promise<unknown>;`);
        }
        else {
            lines.push(`  /** ${head}.${tail} — POST /${head.toLowerCase()}/${tail}. Round 3: substitute real input/output types. */`);
            lines.push(`  ${tail}(input: unknown): Promise<unknown>;`);
        }
    }
    lines.push('};');
    lines.push('');
    // Endpoint resolution helper
    lines.push(`function resolveBaseUrl(): string {`);
    lines.push(`  const url = process.env.${envVar};`);
    lines.push(`  if (!url) throw new Error('${envVar} env var is not set; required for ${importEntry.from} client');`);
    lines.push(`  return url.replace(/\\/+$/, '');`);
    lines.push(`}`);
    lines.push('');
    // POST helper
    lines.push(`async function postJson(path: string, body: unknown): Promise<unknown> {`);
    lines.push(`  const baseUrl = resolveBaseUrl();`);
    lines.push(`  const r = await fetch(\`\${baseUrl}\${path}\`, {`);
    lines.push(`    method: 'POST',`);
    lines.push(`    headers: { 'Content-Type': 'application/json' },`);
    lines.push(`    body: JSON.stringify(body),`);
    lines.push(`  });`);
    lines.push(`  if (!r.ok) {`);
    lines.push(`    const errText = await r.text().catch(() => '<no body>');`);
    lines.push(`    throw new Error(\`\${path} failed: \${r.status} \${r.statusText} — \${errText}\`);`);
    lines.push(`  }`);
    lines.push(`  return r.json();`);
    lines.push(`}`);
    lines.push('');
    // Constant
    lines.push(`export const ${clientName}Client: ${interfaceName}Client = {`);
    const seenConst = new Set();
    for (const entry of selectEntries) {
        if (seenConst.has(entry))
            continue;
        seenConst.add(entry);
        const { head, tail } = parseSelectEntry(entry);
        if (tail === undefined) {
            lines.push(`  ${head}(...args: unknown[]): Promise<unknown> {`);
            lines.push(`    return postJson('/${head.toLowerCase()}', args);`);
            lines.push(`  },`);
        }
        else {
            lines.push(`  ${tail}(input: unknown): Promise<unknown> {`);
            lines.push(`    return postJson('/${head.toLowerCase()}/${tail}', input);`);
            lines.push(`  },`);
        }
    }
    lines.push('};');
    lines.push('');
    return lines.join('\n');
}
// ─── gRPC client generator (Round 2 stub) ─────────────────────────────────────
function generateGrpcClient(ctx, clientName, interfaceName) {
    const { importEntry } = ctx;
    const selectEntries = importEntry.select ?? [];
    const lines = [];
    lines.push('// FACTORY-EMITTED — DO NOT EDIT (R8)');
    lines.push(`// Generated by service-runtime emitter — V2 Phase 3`);
    lines.push(`// Provider: ${importEntry.from} (protocol: grpc)`);
    lines.push(`// TODO (Round 3): wire actual gRPC stub from .proto descriptor.`);
    lines.push(`//   Until then methods throw at call-time. Interface compiles cleanly.`);
    lines.push('');
    lines.push(`export type ${interfaceName}Client = {`);
    const seen = new Set();
    for (const entry of selectEntries) {
        if (seen.has(entry))
            continue;
        seen.add(entry);
        const { tail } = parseSelectEntry(entry);
        if (tail === undefined) {
            lines.push(`  ${entry}(...args: unknown[]): Promise<unknown>;`);
        }
        else {
            lines.push(`  ${tail}(...args: unknown[]): Promise<unknown>;`);
        }
    }
    lines.push('};');
    lines.push('');
    lines.push(`export const ${clientName}Client: ${interfaceName}Client = {`);
    const seen2 = new Set();
    for (const entry of selectEntries) {
        if (seen2.has(entry))
            continue;
        seen2.add(entry);
        const { tail } = parseSelectEntry(entry);
        const methodName = tail ?? entry;
        lines.push(`  ${methodName}(..._args: unknown[]): Promise<unknown> {`);
        lines.push(`    throw new Error('gRPC stub for ${importEntry.from}.${methodName}: proto wiring is Round 3 TODO');`);
        lines.push(`  },`);
    }
    lines.push('};');
    lines.push('');
    return lines.join('\n');
}
// ─── MCP client generator (Round 2 stub) ──────────────────────────────────────
function generateMcpClient(ctx, clientName, interfaceName) {
    const { importEntry } = ctx;
    const selectEntries = importEntry.select ?? [];
    const lines = [];
    lines.push('// FACTORY-EMITTED — DO NOT EDIT (R8)');
    lines.push(`// Generated by service-runtime emitter — V2 Phase 3`);
    lines.push(`// Provider: ${importEntry.from} (protocol: mcp)`);
    lines.push(`// TODO (Round 3): wire @modelcontextprotocol/sdk client + stdio transport.`);
    lines.push(`//   Until then methods throw at call-time. Interface compiles cleanly.`);
    lines.push('');
    lines.push(`export type ${interfaceName}Client = {`);
    const seen = new Set();
    for (const entry of selectEntries) {
        if (seen.has(entry))
            continue;
        seen.add(entry);
        const { tail } = parseSelectEntry(entry);
        if (tail === undefined) {
            lines.push(`  ${entry}(...args: unknown[]): Promise<unknown>;`);
        }
        else {
            lines.push(`  ${tail}(...args: unknown[]): Promise<unknown>;`);
        }
    }
    lines.push('};');
    lines.push('');
    lines.push(`export const ${clientName}Client: ${interfaceName}Client = {`);
    const seen2 = new Set();
    for (const entry of selectEntries) {
        if (seen2.has(entry))
            continue;
        seen2.add(entry);
        const { tail } = parseSelectEntry(entry);
        const methodName = tail ?? entry;
        lines.push(`  ${methodName}(..._args: unknown[]): Promise<unknown> {`);
        lines.push(`    throw new Error('MCP stub for ${importEntry.from}.${methodName}: SDK wiring is Round 3 TODO');`);
        lines.push(`  },`);
    }
    lines.push('};');
    lines.push('');
    return lines.join('\n');
}
// ─── Server-bind generator (Fastify route scaffold) ───────────────────────────
function generateServerBind(ctx, interfaceName) {
    const { importEntry } = ctx;
    const selectEntries = importEntry.select ?? [];
    const lines = [];
    lines.push('// FACTORY-EMITTED — DO NOT EDIT (R8)');
    lines.push(`// Server-bind scaffold for ${importEntry.from} — V2 Phase 3`);
    lines.push(`// One Fastify route per dotted op. Route bodies throw a clear error;`);
    lines.push(`// Round 3 wires the actual spec operations into each handler.`);
    lines.push('');
    lines.push(`import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';`);
    lines.push('');
    lines.push(`export function register${interfaceName}Routes(fastify: FastifyInstance): void {`);
    const seen = new Set();
    for (const entry of selectEntries) {
        if (seen.has(entry))
            continue;
        seen.add(entry);
        const { head, tail } = parseSelectEntry(entry);
        const path = tail ? `/${head.toLowerCase()}/${tail}` : `/${head.toLowerCase()}`;
        lines.push(`  fastify.post('${path}', async (req: FastifyRequest, _reply: FastifyReply) => {`);
        lines.push(`    // TODO (Round 3): wire spec operation '${entry}' into this handler.`);
        lines.push(`    throw new Error('handler not implemented — Round 3 wires the spec operation in: ${entry}');`);
        lines.push(`  });`);
    }
    lines.push(`}`);
    lines.push('');
    return lines.join('\n');
}
// ─── Service emitter ──────────────────────────────────────────────────────────
/**
 * Service-runtime emitter implementation.
 *
 * One call to `emit()` processes a single `import:` entry. Produces:
 *   - 1 client file under `clients/<provider-filename>.ts`
 *   - 1 server-bind file under `servers/<provider-filename>-server.ts` (only
 *     when factory.v2metadata.serverTemplate is non-null)
 */
export class ServiceRuntimeEmitter {
    topology = 'service';
    async emit(ctx) {
        const { importEntry, factory } = ctx;
        const selectEntries = importEntry.select ?? [];
        const fileName = toFileName(importEntry.from);
        const clientName = toClientName(importEntry.from);
        const interfaceName = toInterfaceName(importEntry.from);
        const protocol = resolveProtocol(ctx);
        const envVar = resolveEndpointEnv(ctx);
        // Generate client content based on protocol.
        let clientContent;
        if (protocol === 'grpc') {
            clientContent = generateGrpcClient(ctx, clientName, interfaceName);
        }
        else if (protocol === 'mcp') {
            clientContent = generateMcpClient(ctx, clientName, interfaceName);
        }
        else {
            clientContent = generateRestClient(ctx, clientName, interfaceName);
        }
        const files = [
            { path: `clients/${fileName}.ts`, content: clientContent },
        ];
        // Conditional server-bind file.
        if (shouldEmitServerBind(ctx)) {
            const serverContent = generateServerBind(ctx, interfaceName);
            files.push({ path: `servers/${fileName}-server.ts`, content: serverContent });
        }
        // Consumer-side import entry.
        const consumerImport = {
            from: `./clients/${fileName}`,
            specifier: `${clientName}Client`,
        };
        const notes = [
            `Deployment hint: deployments.<env>.instances.services.${clientName} with config: { endpoint: { env: '${envVar}' } } (factory: ${factory.name}, protocol: ${protocol}).`,
        ];
        if (selectEntries.length === 0) {
            notes.push(`Warning: import entry from '${importEntry.from}' has empty select: — no client members emitted.`);
        }
        if (protocol === 'grpc') {
            notes.push(`TODO (Round 3): gRPC stub for ${importEntry.from} — proto wiring deferred. Methods throw at call-time.`);
        }
        else if (protocol === 'mcp') {
            notes.push(`TODO (Round 3): MCP client for ${importEntry.from} — @modelcontextprotocol/sdk wiring deferred. Methods throw at call-time.`);
        }
        const reviewNotes = [];
        if (protocol === 'grpc') {
            reviewNotes.push(`gRPC stub for ${importEntry.from} — proto wiring deferred; methods throw at call-time.`);
        }
        else if (protocol === 'mcp') {
            reviewNotes.push(`MCP client for ${importEntry.from} — @modelcontextprotocol/sdk wiring deferred; methods throw at call-time.`);
        }
        const deploymentSuggestion = {
            provider: importEntry.from,
            consumerComponent: ctx.consumerComponent?.name,
            category: 'services',
            key: clientName,
            config: {
                endpoint: { env: envVar },
                protocol,
            },
            factory: factory.name,
            topology: 'service',
            ...(reviewNotes.length > 0 ? { reviewNotes } : {}),
        };
        return {
            files,
            imports: [consumerImport],
            notes,
            deploymentSuggestion,
        };
    }
}
//# sourceMappingURL=service.js.map