/**
 * slim-report-summary.ts
 *
 * Pure transformer: a full `ScoreSummary` (the shape of `score-summary.json`)
 * into the slim `ReportSummary` that Phase C of D0033 publishes on the
 * Report Content-Lake document. Inlined prose and long arrays are replaced
 * with `id` references to external artifacts (graderJudgments, failureModes,
 * gapReport, traces).
 *
 * **The `id` principle**: every slim reference carries the manifest entry
 * key of the external artifact it points at, produced by the descriptor's
 * own `formatEntryKey(axes)`. Studio looks the id up in `Report.artifactManifest`
 * to get the preview, and hydrates the full payload on drill-down.
 *
 * @see docs/decisions/D0033-unified-run-anchored-artifact-capture.md (§ M7)
 * @see docs/work-items/W0051-report-slim-down-manifest-preview-hooks.json
 */
import { splitTaskVariant } from "../artifact-capture/association.js";
import { ARTIFACT_REGISTRY } from "../artifact-registry.js";
/**
 * Transform a full pipeline `ScoreSummary` into its slim Report counterpart.
 *
 * Each of the four heavy fields is reshaped independently; everything else
 * flows through untouched via structural spread. Pure function — the input
 * summary is not mutated.
 *
 * @param mode The evaluation mode (used to populate the `mode` axis on slim
 *             judgment / failure-mode ids). `score-summary.json` carries
 *             the mode in `evaluationMode` but the publisher supplies it
 *             explicitly to keep the helper independent of that field.
 */
export function buildSlimReportSummary(summary, mode) {
    const { agentBehavior: fullAgentBehavior, failureModes: fullFailureModes, lowScoringJudgments: fullJudgments, recommendations: fullRecommendations, ...rest } = summary;
    return {
        ...rest,
        ...(fullJudgments
            ? { lowScoringJudgments: slimJudgments(fullJudgments, mode) }
            : {}),
        ...(fullFailureModes
            ? { failureModes: slimFailureModes(fullFailureModes, mode) }
            : {}),
        ...(fullRecommendations
            ? { recommendations: slimRecommendations(fullRecommendations) }
            : {}),
        ...(fullAgentBehavior
            ? { agentBehavior: slimAgentBehavior(fullAgentBehavior) }
            : {}),
    };
}
// ---------------------------------------------------------------------------
// Judgments — axes {mode, task, model, grader}
// ---------------------------------------------------------------------------
function slimJudgments(full, defaultMode) {
    const descriptor = ARTIFACT_REGISTRY.graderJudgments;
    const formatKey = descriptor.formatEntryKey;
    if (!formatKey) {
        throw new Error("slimJudgments: graderJudgments descriptor is missing formatEntryKey");
    }
    return full.map((j) => {
        const { task, variant } = splitTaskVariant(j.taskId);
        // The judgment's task variant overrides the caller-supplied mode when
        // present — a single summary run may carry judgments from both the
        // gold and baseline halves of a literacy run.
        const mode = variant ?? defaultMode;
        const graderId = j.dimension;
        const id = formatKey({
            mode,
            task,
            model: j.modelId,
            grader: graderId,
        });
        const reason = typeof j.reason === "string" ? j.reason : "";
        const reasonPreview = reason.length > 280 ? reason.slice(0, 280) : reason;
        return {
            id,
            taskId: j.taskId,
            modelId: j.modelId,
            graderId,
            // Legacy alias — pre-W0051 Studio readers access `.dimension`.
            // Removed in Slice 6 when those consumers migrate.
            dimension: graderId,
            score: j.score,
            // Inline fallback for offline/cache-skipped runs whose manifest is
            // empty. Truncated to match the graderJudgments descriptor preview
            // schema so either source renders identically at the same cap.
            reasonPreview,
            // Legacy alias — same truncated text under the old name.
            reason: reasonPreview,
        };
    });
}
// ---------------------------------------------------------------------------
// Failure modes — axes {mode, category}, top-N by count
// ---------------------------------------------------------------------------
const FAILURE_MODE_TOP_N = 5;
function slimFailureModes(full, defaultMode) {
    const descriptor = ARTIFACT_REGISTRY.failureModes;
    const formatKey = descriptor.formatEntryKey;
    if (!formatKey) {
        throw new Error("slimFailureModes: failureModes descriptor is missing formatEntryKey");
    }
    const counts = { ...full.summary };
    const nonZero = Object.entries(counts).filter(([, n]) => n > 0);
    nonZero.sort((a, b) => b[1] - a[1]);
    const topTitles = nonZero
        .slice(0, FAILURE_MODE_TOP_N)
        .map(([category, count]) => ({
        id: formatKey({ mode: defaultMode, category }),
        category,
        severity: severityForCount(count),
        title: toTitleCase(category),
        count,
    }));
    return {
        counts,
        topTitles,
        totalJudgments: full.totalJudgments,
        classificationRate: full.classificationRate,
    };
}
function severityForCount(count) {
    if (count >= 10)
        return "critical";
    if (count >= 5)
        return "high";
    if (count >= 2)
        return "medium";
    return "low";
}
function toTitleCase(id) {
    return id
        .split("-")
        .map((w) => (w.length === 0 ? w : w[0].toUpperCase() + w.slice(1)))
        .join(" ");
}
// ---------------------------------------------------------------------------
// Recommendations — bulk artifact; id is a synthetic area--mode composite
// ---------------------------------------------------------------------------
const RECOMMENDATION_TOP_N = 3;
function slimRecommendations(full) {
    // Cache-hit pass-through: when the pipeline restores a previously
    // published report on a remote cache hit, `score-summary.json` carries
    // recommendations in their already-slim shape (no `.gaps` field).
    // Re-slimming would crash on `for (gap of undefined)`; the slim shape
    // has no full-fidelity data to recover, so we return it verbatim.
    if (!Array.isArray(full.gaps)) {
        return full;
    }
    const fullReport = full;
    const counts = {};
    for (const gap of fullReport.gaps) {
        counts[gap.area] = (counts[gap.area] ?? 0) + 1;
    }
    // Sort by priority descending, break ties by estimatedLift.
    const sorted = [...fullReport.gaps].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0) ||
        (b.estimatedLift ?? 0) - (a.estimatedLift ?? 0));
    const top3 = sorted
        .slice(0, RECOMMENDATION_TOP_N)
        .map((g) => ({
        id: gapId(g),
        area: g.area,
        title: toTitleCase(g.failureMode),
        priority: g.priority,
    }));
    return {
        counts,
        top3,
        totalGaps: fullReport.gaps.length,
        totalPotentialLift: fullReport.totalPotentialLift,
    };
}
/**
 * Synthetic composite id for a gap — the gapReport artifact is bulk, so a
 * per-gap manifest entry does not exist. This id lets Studio deduplicate
 * gaps and deep-link within the bulk artifact.
 */
function gapId(g) {
    return `${sanitizeIdSegment(g.area)}--${sanitizeIdSegment(g.failureMode)}`;
}
function sanitizeIdSegment(s) {
    return s
        .trim()
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, "-")
        .replace(/^-+|-+$/g, "");
}
// ---------------------------------------------------------------------------
// Agent behavior — counts + first-N samples per feature
// ---------------------------------------------------------------------------
const BEHAVIOR_SAMPLE_N = 5;
function slimAgentBehavior(full) {
    return full.map((f) => {
        const uniqueQueries = dedupe(f.searchQueries);
        const uniqueSlugs = dedupe(f.docSlugsVisited);
        return {
            feature: f.feature,
            avgDocPagesVisited: f.avgDocPagesVisited,
            avgNetworkTimeMs: f.avgNetworkTimeMs,
            avgSearchesPerformed: f.avgSearchesPerformed,
            tasksWithBehaviorData: f.tasksWithBehaviorData,
            externalDomains: f.externalDomains,
            searchQueriesCount: uniqueQueries.length,
            searchQueriesSample: uniqueQueries.slice(0, BEHAVIOR_SAMPLE_N),
            docSlugsVisitedCount: uniqueSlugs.length,
            docSlugsVisitedSample: uniqueSlugs.slice(0, BEHAVIOR_SAMPLE_N),
        };
    });
}
function dedupe(items) {
    return [...new Set(items)];
}
