/**
 * resolve-per-area-baselines.ts
 *
 * Pure per-area baseline resolution for pipeline auto-comparison
 * (W-resolve-per-area-baselines).
 *
 * The whole-report selector ({@link selectComparableBaseline}) picks a single
 * prior report that must cover EVERY area the current run scored. Production
 * runs have heterogeneous area coverage — full 9-area sweeps, single-area
 * ad-hoc runs, release-scoped runs — so a single fully-covering baseline often
 * does not exist, and the whole-report selector then declines to compare at all
 * (the report publishes with no deltas).
 *
 * This module generalizes selection to the architectural model: resolve EACH
 * area's baseline independently. "For `content-lake`, find the most recent
 * comparable prior report that actually scored `content-lake`." Deltas then
 * stay meaningful no matter how runs are sliced.
 *
 * The comparability gate is identical to whole-report selection — same
 * `classification` (trend series, D0037) and `variant` (scoring methodology) —
 * reused via {@link filterComparableCandidates} so the two paths can never
 * drift. The only difference is the area predicate: whole-report needs one
 * candidate covering all areas; per-area needs, for each area, the most recent
 * candidate covering THAT area.
 *
 * In the homogeneous case (every current area was scored by the most recent
 * comparable run) every area resolves to that one run, so per-area resolution
 * collapses to exactly the whole-report result — the per-area deltas and
 * overall delta are value-identical, and only the `baselineSources` attribution
 * is added.
 *
 * Pure: no I/O, no clock, no Sanity client. The caller (`ReportStore`) fetches
 * a slim candidate projection via GROQ and feeds it here; the area predicate
 * can't ride GROQ's indexed `Aggregate` path (array-membership forces a full
 * `Fetch`), so it lives in TypeScript where it is also directly unit-testable.
 */
import { filterComparableCandidates, } from "./select-baseline.js";
/**
 * Choose the primary report from a set of per-area sources: the report covering
 * the most areas, tie-broken by most-recent `completedAt`. In the common
 * homogeneous case every area resolves to the same report, so this is simply
 * that report.
 *
 * Exported so the impure caller can recompute the primary from the sources it
 * actually used (after dropping any area whose winning report's `scores[]`
 * disagreed with its `provenance.areas`), keeping `lineage.comparedAgainst` from
 * ever naming a report that contributed no baseline score.
 */
export function pickPrimaryReportId(resolved) {
    const byReport = new Map();
    for (const { reportId, completedAt } of resolved) {
        const prev = byReport.get(reportId);
        if (prev) {
            prev.count += 1;
        }
        else {
            byReport.set(reportId, { completedAt, count: 1 });
        }
    }
    let primary = resolved[0].reportId;
    let best = byReport.get(primary);
    for (const [reportId, stats] of byReport) {
        if (stats.count > best.count ||
            (stats.count === best.count && stats.completedAt > best.completedAt)) {
            primary = reportId;
            best = stats;
        }
    }
    return primary;
}
/**
 * Resolve each current-run area's baseline independently from a candidate list.
 *
 * The candidate list need not be pre-sorted — the shared comparability gate
 * sorts most-recent-first. For each current area, the first (most recent)
 * comparable candidate whose area set includes that area wins. Areas no
 * comparable candidate scored are returned in `unresolved`.
 *
 * @see filterComparableCandidates — the shared classification + variant gate
 * @see selectComparableBaseline — the whole-report counterpart
 */
export function resolvePerAreaBaselines(current, candidates) {
    const allUnresolved = (reason) => ({
        primaryReportId: null,
        reason,
        resolved: [],
        unresolved: [...current.areas],
    });
    const gate = filterComparableCandidates(current, candidates);
    if (!gate.ok) {
        return allUnresolved(gate.reason);
    }
    // gate.ordered is already most-recent-first (the gate's tested contract), so
    // the first candidate covering an area is that area's most recent baseline.
    const ordered = gate.ordered;
    const resolved = [];
    const unresolved = [];
    for (const area of current.areas) {
        const winner = ordered.find((c) => c.areas.includes(area));
        if (winner) {
            resolved.push({
                area,
                completedAt: winner.completedAt,
                reportId: winner.id,
            });
        }
        else {
            unresolved.push(area);
        }
    }
    if (resolved.length === 0) {
        return {
            primaryReportId: null,
            reason: "no-area-coverage",
            resolved,
            unresolved,
        };
    }
    return {
        primaryReportId: pickPrimaryReportId(resolved),
        reason: "resolved",
        resolved,
        unresolved,
    };
}
