/**
 * Per-action body emitter — Phase 1 scaffolding for the Realize L3
 * per-action redesign (proposal: 2026-05-07-REALIZE-L3-PER-ACTION).
 *
 * Reframes the unit of work for action body emission from per-step
 * (one helper per spec step bullet, glued by step1Result → step2Result
 * envelopes) to per-action (one coherent method body per action, with
 * step bullets as spec input rather than code-output structure).
 *
 * Three branches, in priority order:
 *
 * 1. **Full-convention coverage** — the L2 convention matcher returns
 *    a non-AI-fallback `MatchResult` for every step in the action.
 *    The matched per-step TS snippets are assembled deterministically
 *    into the method body (no LLM call).
 *
 * 2. **LLM-driven emission** — at least one step lacks convention
 *    coverage AND a real LLM is available. We delegate to the
 *    microcall-orchestrator hook with the convention-matched scaffolding
 *    as pre-baked context (DR3-A: pre-baked TS shown to LLM as
 *    scaffolding, structural validator post-emission). Phase 1 stubs
 *    the prompt name (`realize-action.prompt.yaml`) — that asset lands
 *    in Phase 2; here we just thread the call site.
 *
 * 3. **Stub-mode body** (DR4-γ) — when neither full-convention nor LLM
 *    is available, emit a body that:
 *      a. Throws `Error("<action>: not implemented")` at function entry
 *         so the failure is unambiguous at runtime.
 *      b. Preserves the convention-matched scaffolding as a code-block
 *         comment beneath the throw, with explicit "Gaps:" entries
 *         enumerating the steps the matcher couldn't cover.
 *    Captures stub-mode review value without misleading partial execution.
 *
 * Phase 1 scope:
 *   - Just the orchestrator + emitter scaffolding. The realize-time
 *     wiring (replacing the per-step `.ai.ts` emission path), the
 *     `realize-action.prompt.yaml` asset, and the actual LLM call
 *     plumbing all land in subsequent phases (3, 2, and 3 respectively).
 *   - Out of scope: `.ai.ts` deletion (DR2: keep the filename, repurpose
 *     content shape — Phase 3 work).
 */
/**
 * Default LLM-availability check. Mirrors `model-resolver.resolveProviderId`'s
 * logic without importing it (avoids dragging the AI subpath into
 * realize for a cheap predicate). When a real implementation lands in
 * Phase 3, it will likely just call `resolveProviderId() !== 'stub'`.
 */
function defaultCanCallLLM() {
    const explicit = process.env.SPECVERSE_AI_PROVIDER;
    if (explicit === 'stub')
        return false;
    if (process.env.MCP_SERVER === '1')
        return false;
    // If unset, we can't be sure — be conservative (false) so realize
    // doesn't accidentally fire LLM calls in environments that aren't
    // configured for AI. Tests inject `canCallLLM` explicitly.
    return Boolean(explicit) || Boolean(process.env.ANTHROPIC_API_KEY);
}
/**
 * Run every step in the action through the L2 convention matcher.
 * Returns `MatchResult | null` per step in spec order (`null` when the
 * action has no steps to dispatch).
 *
 * The matcher walks conventions in order; first match wins. Unmatched
 * steps compute the AI-fallback shape (`functionName + inputs + resultVar`)
 * deterministically — they're still part of `MatchResult`, just with
 * `matched: false`. We rely on that flag to detect coverage.
 */
function matchSteps(action, context) {
    if (!action.steps || action.steps.length === 0)
        return [];
    if (!context.matcher || !context.conventions) {
        // No matcher / no conventions → every step is "unmatched" for
        // emitter purposes. We still return MatchResult shapes so the LLM /
        // stub branches see the same per-step structure as a real ORM
        // dispatch.
        return action.steps.map((_step, i) => ({
            matched: false,
            call: `    // Step ${i + 1}: ${action.steps[i]} [unmatched]`,
            functionName: undefined,
            inputs: undefined,
            resultVar: undefined,
        }));
    }
    const aiArgsExpr = context.aiArgsExpr ??
        ((inputs) => (inputs.length > 0 ? `{ ${inputs.join(', ')} }` : '{}'));
    const declaredVars = new Set();
    const parameterNames = action.parameters ? Object.keys(action.parameters) : [];
    const results = [];
    for (let i = 0; i < action.steps.length; i++) {
        const step = action.steps[i];
        const ctx = {
            modelName: context.modelName,
            serviceName: action.ownerName,
            operationName: action.name,
            stepNum: i + 1,
            parameterNames,
            declaredVars,
        };
        const result = context.matcher(step, ctx, context.conventions, aiArgsExpr);
        results.push(result);
    }
    return results;
}
/**
 * When every step is convention-matched, assemble the method body
 * deterministically by joining the matcher's `.call` strings.
 *
 * The call strings come pre-indented (4 spaces — enough for "inside a
 * method body") and trailing-newline-free. We wrap them with a
 * try-block + a `return` at the end where possible (last step's
 * resultVar). Phase 3 will refine the assembly (typed signatures,
 * error-flow shapes) once the realize-time wiring decides on a single
 * controller / service template path; Phase 1 keeps it minimal so the
 * emitter's contract is stable.
 */
export function assembleFromConventions(action, perStepMatches) {
    const lines = [];
    for (const m of perStepMatches) {
        // m.call already carries indentation. Strip a trailing newline if any.
        lines.push(m.call.replace(/\n$/, ''));
    }
    // Best-effort return statement: when the last step produced a
    // resultVar, return it. The matcher's `.call` for matched conventions
    // doesn't include a return; we add one here so the body is
    // syntactically complete. Unmatched fallbacks still set resultVar
    // but we'd never reach this assembler with an unmatched step.
    const last = perStepMatches[perStepMatches.length - 1];
    if (last?.resultVar) {
        lines.push(`    return ${last.resultVar};`);
    }
    return lines.join('\n');
}
/**
 * Emit the γ-shaped stub body (DR4 resolution). The function body opens
 * with a `throw new Error("<action>: not implemented")` so the failure
 * surfaces unambiguously at runtime, and the convention-matched
 * scaffolding is preserved as a code-block comment beneath the throw
 * for reviewer context.
 *
 * Format:
 *   throw new Error("processOrder: not implemented");
 *   /* Convention-matched scaffolding (preserved for review):
 *    *   <pre-baked TS for matched steps>
 *    * Gaps:
 *    *   - <step text for unmatched steps>
 *    *\/
 */
export function emitStubGammaBody(action, perStepMatches) {
    const matchedLines = [];
    const gaps = [];
    for (let i = 0; i < perStepMatches.length; i++) {
        const result = perStepMatches[i];
        const step = action.steps[i] ?? '';
        if (result?.matched) {
            // Indent for readability inside the comment block.
            const callLines = result.call.split('\n').map((ln) => '   *   ' + ln.trimStart());
            matchedLines.push(...callLines);
        }
        else {
            gaps.push(`   *   - ${step}`);
        }
    }
    const lines = [];
    lines.push(`    throw new Error(${JSON.stringify(`${action.name}: not implemented`)});`);
    lines.push('    /* Convention-matched scaffolding (preserved for review):');
    if (matchedLines.length > 0) {
        lines.push(...matchedLines);
    }
    else {
        lines.push('   *   (no steps matched any convention)');
    }
    if (gaps.length > 0) {
        lines.push('   * Gaps:');
        lines.push(...gaps);
    }
    lines.push('   */');
    return lines.join('\n');
}
/**
 * Emit the body for a single action.
 *
 * Branch order:
 *   1. Full-convention coverage  → assembled deterministically.
 *   2. Mixed coverage + LLM available + `llmEmit` returns text → LLM result.
 *   3. Otherwise (stub mode, no LLM, or `llmEmit` returned null) → γ stub.
 *
 * The signature returns just the body (not the wrapping method
 * declaration / params / return type); Phase 3 will integrate this
 * with the controller / service template emitters.
 */
export async function emitActionBody(action, context) {
    const perStepMatches = matchSteps(action, context);
    const allMatched = perStepMatches.length > 0 &&
        perStepMatches.every((m) => m !== null && m.matched === true);
    // Branch 1 — every step has a convention match.
    if (allMatched) {
        return assembleFromConventions(action, perStepMatches);
    }
    // Branch 2 — at least one gap. Try the LLM if available + hook present.
    const canCall = (context.canCallLLM ?? defaultCanCallLLM)();
    if (canCall && context.llmEmit) {
        const matched = perStepMatches.filter((m) => m !== null && m.matched === true);
        const scaffoldingSnippet = matched.map((m) => m.call.replace(/\n$/, '')).join('\n');
        try {
            const llmBody = await context.llmEmit({
                action,
                context,
                perStepMatches,
                scaffoldingSnippet,
                // PLACEHOLDER (Phase 1) — the prompt asset doesn't exist yet.
                // Phase 2 ships `realize-action.prompt.yaml` and Phase 3 wires
                // this constant up to the actual call site.
                promptName: 'realize-action.prompt.yaml',
            });
            if (llmBody && llmBody.trim().length > 0) {
                return llmBody;
            }
            // Fall through to stub when LLM returned null / empty.
        }
        catch (e) {
            // Any LLM error → stub. The inner per-action-llm-emit hook
            // captures its own runPrompt/parse/empty-body cases; this catch
            // picks up everything else (buildPromptValues failures, unhandled
            // rejections, anything thrown OUTSIDE the inner try). Gated behind
            // SPECVERSE_VERBOSE so production runs stay quiet.
            if (process.env.SPECVERSE_VERBOSE) {
                const cls = (e && e.constructor && e.constructor.name) || 'Error';
                const msg = (e && e.message) || String(e);
                const head = String(msg).slice(0, 240).replace(/\s+/g, ' ');
                console.error(`   ✗ per-action emitter outer catch ${action.ownerName}.${action.name} [${cls}]: ${head}`);
            }
        }
    }
    // Branch 3 — γ stub.
    return emitStubGammaBody(action, perStepMatches);
}
//# sourceMappingURL=per-action-emitter.js.map