/**
 * report-cache-state.ts — the single derivation of whether an eval report's
 * displayed cost / tokens / timestamp were inherited from a remote-cache
 * source run, shared by every output surface (Slack, PR-comment markdown,
 * CLI, dashboard) so the classification and source-run attribution are
 * identical everywhere.
 *
 * ## Why this exists
 *
 * When the pipeline serves a run from the remote cache (the eval fingerprint
 * matches a prior published report), the new report inherits the source run's
 * `summary.overall.cost`, `summary.scores`, and `summary.timestamp`, while
 * `durationMs` stays the new run's own short wall-clock. Every renderer shows
 * these side by side, so a $48 / 1M-token figure appears next to a 28-second
 * runtime with no marker that the cost is borrowed. This helper produces the
 * marker.
 *
 * ## The signal (no new persisted field — D0040)
 *
 * The cache-hit branch in `RunEvalStep` copies the source report's artifact
 * manifest forward and stamps `sourceRunId` on every ref via
 * `remapToCacheHitRefs`. So an artifact whose `sourceRunId` is set and differs
 * from the report's own `provenance.runId` was served from another run.
 *
 * ## Why only the cost-bearing artifacts count
 *
 * The classification reads only the artifacts that represent the eval
 * execution whose cost is being displayed: `rawResults` (provider inference +
 * its per-call cost/tokens), `renderedPrompts`, `graderPrompts`,
 * `graderJudgments` (grader cost), and `traces` (agentic execution).
 *
 * `testOutputs` is deliberately EXCLUDED. It is restored from a prior run
 * independently of cost — real fresh-cost reports carry a foreign
 * `testOutputs.sourceRunId` while their `rawResults` ran fresh (observed on
 * production reports `019f03ae` and `019f07d8`: foreign `testOutputs`, fresh
 * everything else, genuine sub-$0.50 spend). Counting `testOutputs` would
 * mislabel those fresh runs as cached. Bulk bookkeeping artifacts
 * (`pipelineContext`, `scoreSummary`, `runManifest`, …) are excluded because
 * the new run always produces them fresh, and post-hoc artifacts
 * (`diagnosis`) are excluded because `remapToCacheHitRefs` never stamps them.
 *
 * @see docs/decisions/D0040-artifact-ref-source-run-id.md
 * @see packages/eval/src/pipeline/cache-hit-restore.ts (remapToCacheHitRefs)
 */
/**
 * Eval-execution artifacts whose `sourceRunId` reflects whether the displayed
 * cost was inherited. `satisfies readonly ArtifactType[]` ties the list to the
 * registry so renaming or removing an artifact type is a compile error here.
 */
const COST_BEARING_ARTIFACTS = [
    "rawResults",
    "renderedPrompts",
    "graderPrompts",
    "graderJudgments",
    "traces",
];
/**
 * Derive cache state from a run id and an artifact manifest. The single source
 * of truth for the classification — every surface consumes this (directly, or
 * via {@link reportCacheState} for domain `Report` callers).
 */
export function classifyCacheState(input) {
    const manifest = input.artifactManifest;
    if (!manifest)
        return { kind: "fresh" };
    let presentCount = 0;
    let cachedCount = 0;
    const sources = new Set();
    for (const type of COST_BEARING_ARTIFACTS) {
        const ref = manifest[type];
        if (ref == null)
            continue;
        presentCount += 1;
        const source = ref.sourceRunId;
        if (source != null && source !== input.currentRunId) {
            cachedCount += 1;
            sources.add(source);
        }
    }
    if (cachedCount === 0)
        return { kind: "fresh" };
    const sourceRunIds = [...sources].sort();
    return cachedCount === presentCount
        ? { kind: "fully-cached", sourceRunIds }
        : { kind: "partially-cached", sourceRunIds };
}
/**
 * {@link classifyCacheState} over a domain `Report` (or any value carrying
 * `provenance.runId` and an optional `artifactManifest`). This is the entry
 * point for the Slack and markdown renderers, which operate on the domain
 * `Report`.
 */
export function reportCacheState(report) {
    return classifyCacheState({
        currentRunId: report.provenance.runId,
        artifactManifest: report.artifactManifest,
    });
}
/**
 * Returns a {@link CacheProvenanceNote} for a cache-served report, or `null`
 * for a fresh report (no indicator needed).
 */
export function describeCacheState(state) {
    if (state.kind === "fresh")
        return null;
    const runs = state.sourceRunIds;
    const sourceAttribution = runs.length === 1
        ? `source run ${runs[0]}`
        : `source runs ${runs.join(", ")}`;
    if (state.kind === "fully-cached") {
        return {
            kind: "fully-cached",
            label: "Fully cached",
            sourceRunIds: runs,
            sourceAttribution,
            summary: `Cost, tokens, and scores were served from the remote cache (${sourceAttribution}); only runtime reflects this run.`,
        };
    }
    return {
        kind: "partially-cached",
        label: "Partially cached",
        sourceRunIds: runs,
        sourceAttribution,
        summary: `Some eval modes were served from the remote cache (${sourceAttribution}); cost mixes inherited and freshly-spent.`,
    };
}
