/**
 * Realize-phase audit recorder - module-level hook that the matcher
 * functions in libs/instance-factories/services/templates/{prisma,
 * mongodb-native, postgres-native}/step-conventions.ts invoke to record
 * their dispatch decisions at the moment they're made.
 *
 * Forward-logging discipline (see
 * specverse-self/docs/plans/2026-05-03-PIPELINE-AUDIT-TRAIL-INSTRUMENTATION.md):
 * each step's level-2 (convention match) vs level-3 (AI fallback) decision
 * is captured by the same call site that uses the result for code
 * generation. No parallel walk, no inferred verdict.
 *
 * Constraint: module-level state is single-recorder-per-process. realize
 * is single-shot per CLI invocation, so this is currently fine. If we
 * later run multiple realize phases concurrently in one process, switch
 * to AsyncLocalStorage-backed context.
 */
let activeRecorder = null;
/** Attach a recorder for the duration of a realize phase. Pass null to clear. */
export function setRealizeRecorder(r) {
    activeRecorder = r;
}
/** Called by step-conventions.ts after each matcher decision. No-op when
 *  no recorder is attached (the common case during normal generation
 *  outside the realize orchestrator). */
export function recordRealizeStep(d) {
    activeRecorder?.recordStepResolution(d);
}
export function recordRealizeModelFactory(args) {
    activeRecorder?.recordModelFactory(args);
}
export function recordRealizeCollision(args) {
    activeRecorder?.recordCollision(args);
}
export function recordRealizeFactoryMappingMiss(args) {
    activeRecorder?.recordFactoryMappingMiss(args);
}
export class InMemoryRealizeRecorder {
    steps = [];
    models = [];
    collisions = [];
    factoryMisses = [];
    recordStepResolution(d) { this.steps.push(d); }
    recordModelFactory(args) { this.models.push(args); }
    recordCollision(args) { this.collisions.push(args); }
    recordFactoryMappingMiss(args) { this.factoryMisses.push(args); }
    /** Build the realize-decisions.json + realize-coverage.json shapes. */
    build() {
        // Deduplicate steps. The pre-scan + AI-behaviors-gen pass over the same
        // spec; both record. We keep one record per (ownerName, actionName,
        // stepOrdinal) - preferring the controller-gen / service-gen phase
        // since that's the actual code-emission decision.
        const phasePriority = {
            'controller-gen': 0,
            'service-gen': 0,
            'ai-behaviors-gen': 1,
            'pre-scan': 2,
        };
        const stepKey = (s) => `${s.ownerName}::${s.actionName}::${s.stepOrdinal}`;
        const dedupedSteps = new Map();
        for (const s of this.steps) {
            const k = stepKey(s);
            const existing = dedupedSteps.get(k);
            if (!existing || (phasePriority[s.generatorPhase] ?? 99) < (phasePriority[existing.generatorPhase] ?? 99)) {
                dedupedSteps.set(k, s);
            }
        }
        const allSteps = [...dedupedSteps.values()];
        // Group steps by action.
        const actionMap = new Map();
        for (const s of allSteps) {
            const fqn = `${s.ownerName}.${s.actionName}`;
            let entry = actionMap.get(fqn);
            if (!entry) {
                entry = { fqn, ordinals: new Map() };
                actionMap.set(fqn, entry);
            }
            entry.ordinals.set(s.stepOrdinal, s);
        }
        const actions = [];
        for (const { fqn, ordinals } of actionMap.values()) {
            const ordered = [...ordinals.values()].sort((a, b) => a.stepOrdinal - b.stepOrdinal);
            let L2 = 0, L3 = 0;
            const stepResolutions = ordered.map((s) => {
                const level = s.matched ? 2 : 3;
                if (level === 2)
                    L2++;
                else
                    L3++;
                return {
                    ordinal: s.stepOrdinal,
                    bullet: s.stepText,
                    level: level,
                    convention: s.helperMethod,
                    aiFunctionName: s.aiFunctionName,
                    generatorPhase: s.generatorPhase,
                };
            });
            actions.push({ fqn, stepCount: ordered.length, stepResolutions, summary: { L2, L3 } });
        }
        const decisions = {
            schemaVersion: '1.0',
            stage: 'realize',
            models: this.models.map((m) => ({ fqn: m.modelFqn, factory: m.factory, level: 1 })),
            actions,
            factoryMappings: this.factoryMisses.map((m) => ({
                manifestPath: m.manifestPath,
                manifestRef: m.manifestRef,
                registryMatch: null,
                issue: m.reason,
            })),
            collisions: this.collisions,
        };
        // Coverage aggregation.
        let totalL2 = 0, totalL3 = 0;
        const byOwnerMap = new Map();
        for (const a of actions) {
            totalL2 += a.summary.L2;
            totalL3 += a.summary.L3;
            const owner = a.fqn.split('.')[0];
            let entry = byOwnerMap.get(owner);
            if (!entry) {
                entry = { actions: new Set(), L2: 0, L3: 0 };
                byOwnerMap.set(owner, entry);
            }
            entry.actions.add(a.fqn);
            entry.L2 += a.summary.L2;
            entry.L3 += a.summary.L3;
        }
        const coverage = {
            schemaVersion: '1.0',
            totals: {
                models: this.models.length,
                actions: actions.length,
                stepBullets: totalL2 + totalL3,
                L1Symbols: this.models.length,
                L2Bullets: totalL2,
                L3Bullets: totalL3,
                factoryMappingsResolved: 0, // TODO: track resolved mappings explicitly when registry hooks land
                factoryMappingsUnresolved: this.factoryMisses.length,
                collisions: this.collisions.length,
            },
            byOwner: [...byOwnerMap.entries()].map(([owner, e]) => ({
                owner,
                actions: e.actions.size,
                L2: e.L2,
                L3: e.L3,
            })),
        };
        return { decisions, coverage };
    }
}
//# sourceMappingURL=realize-recorder.js.map