/**
 * emit-symbol-preflight — turns a Promptfoo results file into per-test
 * `symbolPreflight` artifact emissions, one per (run, mode, task, model).
 *
 * Sits next to `emitPerEntryEvalResults` (W0050) in the post-eval phase
 * of `RunEvalStep`. For every test row in the results file we:
 *
 *   1. Pull the candidate's response text out of `result.response.output`.
 *   2. Run `computePreflight` against the framework-level package-surface
 *      manifest using the wired `PackageSurfaceResolver` from the
 *      `AppContext`.
 *   3. Emit the report through the artifact writer at axes
 *      `(run, mode, task, model)` — same axes as `rawResults`.
 *   4. Attach the report onto `state.preflightReports` keyed by the
 *      same axes so the scoring step (Phase 5) can read it without a
 *      second filesystem hop.
 *
 * Non-blocking: a missing resolver, missing manifest, missing response,
 * or per-row exception logs a warning and continues. The deterministic
 * lane is additive; if any of its inputs are missing the LLM rubric
 * still scores the candidate normally.
 */
import { readFileSync } from "node:fs";
import { resolveVariantMode, } from "../../_vendor/ailf-core/index.js";
import { computePreflight } from "./compute-preflight.js";
import { extractCodeBlocks } from "./extract-code-blocks.js";
/** Map a per-row preflight key to a stable string for in-memory lookup. */
export function preflightKey(key) {
    return `${key.run}/${key.mode}/${key.task}/${key.model}`;
}
export async function emitSymbolPreflight(input) {
    const reports = new Map();
    const refs = [];
    if (!input.packageSurface || !input.resolver) {
        // The deterministic lane is additive — when its inputs aren't wired
        // (test contexts, opt-out, partial rollouts) the LLM rubric still
        // grades the candidate. Stay silent.
        return { reports, refs };
    }
    if (input.packageSurface.packages.length === 0) {
        return { reports, refs };
    }
    let raw;
    try {
        raw = JSON.parse(readFileSync(input.resultsPath, "utf-8"));
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        console.warn(`  ⚠️  emitSymbolPreflight: failed to read ${input.resultsPath} — ${message}`);
        return { reports, refs };
    }
    const wrapper = raw.results && "results" in raw.results
        ? raw.results
        : raw;
    const rows = wrapper?.results ?? [];
    if (rows.length === 0)
        return { reports, refs };
    // Track per-(task, model) to dedupe — Promptfoo emits multiple rows
    // for the same candidate when there are multiple assertions, but
    // the preflight only depends on the candidate's text, not the
    // assertion outcome. One report per (task, model) suffices.
    const seen = new Set();
    const emits = [];
    for (let i = 0; i < rows.length; i++) {
        const row = rows[i];
        const rawTaskId = row.testCase?.description ?? "unknown-task";
        const modelId = row.provider?.id ?? row.provider?.label ?? "unknown-model";
        const { mode: axisMode, task: axisTask } = resolveVariantMode(rawTaskId, input.mode);
        const baseAssoc = {
            run: input.ctx.runId,
            mode: axisMode,
            task: axisTask,
            model: modelId,
        };
        const key = preflightKey(baseAssoc);
        if (seen.has(key))
            continue;
        seen.add(key);
        const output = row.response?.output;
        if (typeof output !== "string" || output.length === 0)
            continue;
        // Candidate outputs are typically markdown — prose with fenced code
        // blocks. Feed `parseImports` only the TS/JS source so it doesn't
        // choke on the surrounding markdown. Falls back to the raw text
        // when no recognized fences are present.
        const code = extractCodeBlocks(output);
        let report;
        try {
            report = await computePreflight({
                code,
                candidate: { taskId: axisTask, testIndex: i },
                packageSurface: input.packageSurface,
                resolver: input.resolver,
            });
        }
        catch (err) {
            const message = err instanceof Error ? err.message : String(err);
            console.warn(`  ⚠️  emitSymbolPreflight: computePreflight threw for ${key} — ${message}`);
            continue;
        }
        reports.set(key, report);
        emits.push(input.writer.emit("symbolPreflight", baseAssoc, report));
    }
    const settled = await Promise.all(emits);
    for (const ref of settled)
        refs.push(ref);
    return { reports, refs };
}
