/**
 * Synthesis Router — ADR-007 Implementation
 *
 * Enterprise-grade subcommand-aware routing layer. Intercepts decision-grade
 * subcommands, constructs a prompt, invokes Claude Code, and returns
 * contract-validated output with full traceability metadata.
 *
 * INVARIANTS:
 * - Only DECISION_GRADE_SUBCOMMANDS can trigger Claude Code invocation
 * - Every invocation produces exactly one Claude Code call
 * - All returned data is contract-validated against the mapped schema
 * - SYNTHESIS_FORBIDDEN commands never reach the Claude Code runner
 * - The prompt, run-id, seed, and synthesis class are always in the result
 * - Errors are wrapped with routing context for diagnostics
 * - User-provided correlationId flows through the entire pipeline
 *
 * FORBIDDEN:
 * - Calling Claude Code for SYNTHESIS_FORBIDDEN commands
 * - Writing artifacts (ADR-008 handles that)
 * - Calling the platform API (command executors handle that)
 * - Mutating CLI state
 * - Interactive confirmation (the CLI's gate pipeline handles that)
 */
import { isSynthesisAllowed, isCommitmentGrade, } from '../contracts/adr-command-semantics.js';
import { SYNTHESIS_SCHEMA_MAP, } from '../contracts/adr-006-claude-code-synthesis-runner.js';
import { DECISION_GRADE_SUBCOMMANDS, } from '../contracts/adr-007-subcommand-synthesis-router.js';
import { PROMPT_TEMPLATES } from './prompts/index.js';
import { createClaudeCodeRunner } from '../runtime/claude-code-runner.js';
import { CLIError } from '../errors/index.js';
// ============================================================================
// Constants
// ============================================================================
/** Maximum prompt length before emitting a diagnostic warning (50 KiB) */
const MAX_PROMPT_LENGTH = 50_000;
/** Minimum acceptable prompt length — templates must produce >50 chars */
const MIN_PROMPT_LENGTH = 50;
// ============================================================================
// Input Validation
// ============================================================================
/**
 * Validate a SynthesisRequest has required structure.
 * Catches programming errors at the boundary before touching the runner.
 *
 * @throws CLIError (ECLI-SYNTH-020) on invalid request structure
 */
function validateSynthesisRequest(request) {
    if (!request.command || typeof request.command !== 'string') {
        throw new CLIError({
            code: 'ECLI-SYNTH-020',
            category: 'INTERNAL_ERROR',
            message: 'SynthesisRequest.command is empty or invalid',
            details: { command: request.command },
            module: 'synthesis-router',
            recoverable: false,
            exitCode: 70,
        });
    }
    if (!request.subcommand || typeof request.subcommand !== 'string') {
        throw new CLIError({
            code: 'ECLI-SYNTH-020',
            category: 'INTERNAL_ERROR',
            message: 'SynthesisRequest.subcommand is empty or invalid',
            details: { command: request.command, subcommand: request.subcommand },
            module: 'synthesis-router',
            recoverable: false,
            exitCode: 70,
        });
    }
    if (!request.userContext || typeof request.userContext.traceId !== 'string') {
        throw new CLIError({
            code: 'ECLI-SYNTH-020',
            category: 'INTERNAL_ERROR',
            message: 'SynthesisRequest.userContext.traceId is required',
            details: { command: request.command, subcommand: request.subcommand },
            module: 'synthesis-router',
            recoverable: false,
            exitCode: 70,
        });
    }
}
/**
 * Validate the prompt generated by a template meets minimum requirements.
 *
 * @throws CLIError (ECLI-SYNTH-021) on invalid prompt output
 */
function validatePromptOutput(prompt, key) {
    if (typeof prompt !== 'string' || prompt.length < MIN_PROMPT_LENGTH) {
        throw new CLIError({
            code: 'ECLI-SYNTH-021',
            category: 'INTERNAL_ERROR',
            message: `Prompt template for "${key}" produced ${prompt?.length ?? 0} characters (minimum: ${MIN_PROMPT_LENGTH})`,
            details: {
                key,
                prompt_length: prompt?.length ?? 0,
                minimum_length: MIN_PROMPT_LENGTH,
            },
            module: 'synthesis-router',
            recoverable: false,
            exitCode: 70,
        });
    }
    if (prompt.length > MAX_PROMPT_LENGTH) {
        process.stderr.write(`[synthesis-router] WARNING: Prompt for "${key}" is ${prompt.length} characters ` +
            `(recommended max: ${MAX_PROMPT_LENGTH}). Large prompts may increase latency.\n`);
    }
}
// ============================================================================
// Routing Gates (shared between routeSynthesis and buildSynthesisPrompt)
// ============================================================================
/**
 * Execute the four routing gates common to synthesis and dry-run.
 * Returns the validated template function, schema name, synthesis class,
 * and routing key — or null if the command is not decision-grade.
 *
 * @throws CLIError on missing template or schema (internal configuration errors)
 */
function executeRoutingGates(request) {
    const key = `${request.command}.${request.subcommand}`;
    const correlationId = request.userContext.traceId;
    // Gate 1: Check if this is a decision-grade subcommand
    if (!DECISION_GRADE_SUBCOMMANDS.has(key)) {
        return null;
    }
    // Gate 2: Verify ADR-001 registry agrees
    if (!isSynthesisAllowed(request.command, request.subcommand)) {
        return null;
    }
    // Gate 3: Verify we have a prompt template
    const templateFn = PROMPT_TEMPLATES[key];
    if (!templateFn) {
        throw new CLIError({
            code: 'ECLI-SYNTH-010',
            category: 'INTERNAL_ERROR',
            message: `No prompt template registered for ${key}`,
            details: { command: request.command, subcommand: request.subcommand },
            module: 'synthesis-router',
            correlationId,
            recoverable: false,
            exitCode: 70,
        });
    }
    // Gate 4: Verify we have a schema mapping
    const schemaName = SYNTHESIS_SCHEMA_MAP[key];
    if (!schemaName) {
        throw new CLIError({
            code: 'ECLI-SYNTH-011',
            category: 'INTERNAL_ERROR',
            message: `No contract schema mapped for ${key}`,
            details: { command: request.command, subcommand: request.subcommand },
            module: 'synthesis-router',
            correlationId,
            recoverable: false,
            exitCode: 70,
        });
    }
    // Determine synthesis governance class from ADR-001 registry
    const synthesisClass = isCommitmentGrade(request.command, request.subcommand)
        ? 'COMMITMENT_GRADE'
        : 'SYNTHESIS_REQUIRED';
    return { key, templateFn, schemaName, synthesisClass };
}
/**
 * Generate a run-id, seed, and prompt from a validated routing gate result.
 */
function generatePrompt(request, gate) {
    const runId = `run-${crypto.randomUUID()}`;
    const seed = Math.floor(Math.random() * 1_000_000);
    const promptArgs = {
        runId,
        seed,
        command: request.command,
        subcommand: request.subcommand,
        positionalArgs: request.positionalArgs,
        options: request.options,
        contractSchemaName: gate.schemaName,
    };
    const prompt = gate.templateFn(promptArgs);
    validatePromptOutput(prompt, gate.key);
    return { runId, seed, prompt };
}
// ============================================================================
// Classification Helpers
// ============================================================================
/**
 * Determine the synthesis governance class for a (command, subcommand) pair.
 *
 * @returns 'COMMITMENT_GRADE' or 'SYNTHESIS_REQUIRED' for decision-grade commands, null otherwise
 */
export function getSynthesisClass(command, subcommand) {
    const key = `${command}.${subcommand}`;
    if (!DECISION_GRADE_SUBCOMMANDS.has(key)) {
        return null;
    }
    return isCommitmentGrade(command, subcommand) ? 'COMMITMENT_GRADE' : 'SYNTHESIS_REQUIRED';
}
/**
 * Check if a (command, subcommand) pair is decision-grade.
 * Convenience function for use in CLI dispatch.
 */
export function isDecisionGrade(command, subcommand) {
    if (!subcommand)
        return false;
    return DECISION_GRADE_SUBCOMMANDS.has(`${command}.${subcommand}`);
}
// ============================================================================
// Dry-Run / Prompt Builder
// ============================================================================
/**
 * Build a synthesis prompt without invoking Claude Code.
 *
 * Useful for:
 * - Dry-run mode: inspect what would be sent to Claude Code
 * - Testing: verify prompt generation in isolation
 * - Debugging: see the exact prompt for a given (command, subcommand)
 *
 * Executes all four routing gates but stops before subprocess invocation.
 *
 * @returns SynthesisPromptResult with prompt and metadata, or null if not decision-grade
 * @throws CLIError on missing template or schema (internal configuration errors)
 */
export function buildSynthesisPrompt(request) {
    validateSynthesisRequest(request);
    const gate = executeRoutingGates(request);
    if (gate === null) {
        return null;
    }
    const { runId, seed, prompt } = generatePrompt(request, gate);
    return {
        prompt,
        runId,
        seed,
        key: gate.key,
        contractSchema: gate.schemaName,
        synthesisClass: gate.synthesisClass,
    };
}
// ============================================================================
// Router Implementation
// ============================================================================
/**
 * Route a command through the synthesis pipeline.
 *
 * This is the primary entry point for ADR-007. The flow is:
 *
 * 1. Validate request structure
 * 2. Execute routing gates (decision-grade check, ADR-001 agreement,
 *    template existence, schema mapping)
 * 3. Determine synthesis governance class (SYNTHESIS_REQUIRED or COMMITMENT_GRADE)
 * 4. Generate run-id, seed, and prompt
 * 5. Invoke Claude Code via ADR-006 runner with contract validation
 * 6. Return result with full traceability metadata
 *
 * For non-decision-grade commands, returns null immediately with zero overhead.
 *
 * @returns SynthesisResult if the command is decision-grade, null otherwise
 * @throws CLIError on router configuration errors (ECLI-SYNTH-010/011/020/021)
 * @throws SynthesisError on Claude Code invocation failures (ECLI-SYNTH-001..005)
 */
export function routeSynthesis(request) {
    // Validate request structure
    validateSynthesisRequest(request);
    const verbose = request.flags['verbose'] ?? false;
    const correlationId = request.userContext.traceId;
    // Execute routing gates
    const gate = executeRoutingGates(request);
    if (gate === null) {
        return null;
    }
    if (verbose) {
        process.stderr.write(`[synthesis-router] routing ${gate.key} class=${gate.synthesisClass} ` +
            `schema=${gate.schemaName} correlationId=${correlationId}\n`);
    }
    // Generate prompt
    const { runId, seed, prompt } = generatePrompt(request, gate);
    if (verbose) {
        process.stderr.write(`[synthesis-router] prompt generated: ${prompt.length} chars ` +
            `runId=${runId} seed=${seed}\n`);
    }
    // Invoke Claude Code with contract validation
    let result;
    try {
        const runner = createClaudeCodeRunner();
        result = runner.invoke(prompt, {
            runId,
            seed,
            verbose,
            schemaKey: gate.key,
            correlationId,
        });
    }
    catch (error) {
        // Wrap errors with routing context for diagnostics
        if (error instanceof CLIError) {
            const enrichedDetails = {
                ...error.details,
                routing_key: gate.key,
                routing_synthesis_class: gate.synthesisClass,
                routing_schema: gate.schemaName,
            };
            throw new CLIError({
                code: error.code,
                category: error.category,
                message: `[${gate.key}] ${error.message}`,
                details: enrichedDetails,
                module: error.module,
                correlationId: error.correlationId ?? correlationId,
                recoverable: error.recoverable,
                exitCode: error.exitCode,
                cause: error.cause instanceof Error ? error.cause : undefined,
            });
        }
        // Unknown error — wrap with context
        throw new CLIError({
            code: 'ECLI-SYNTH-099',
            category: 'INTERNAL_ERROR',
            message: `Unexpected error routing ${gate.key}: ${error instanceof Error ? error.message : String(error)}`,
            details: {
                routing_key: gate.key,
                routing_synthesis_class: gate.synthesisClass,
                error_type: error instanceof Error ? error.constructor.name : typeof error,
            },
            module: 'synthesis-router',
            correlationId,
            recoverable: false,
            exitCode: 150,
            cause: error instanceof Error ? error : undefined,
        });
    }
    if (verbose) {
        process.stderr.write(`[synthesis-router] synthesis complete: model=${result.model} ` +
            `duration=${result.durationMs}ms output=${result.rawOutput.length}B\n`);
    }
    return {
        data: result.data,
        runId,
        command: request.command,
        subcommand: request.subcommand,
        model: result.model,
        durationMs: result.durationMs,
        contractSchema: gate.schemaName,
        rawOutput: result.rawOutput,
        synthesisClass: gate.synthesisClass,
        prompt,
    };
}
//# sourceMappingURL=router.js.map