/**
 * ConflictTraceAssembler — groups candidates into traces, filters, and hashes.
 *
 * @module domain/services/strand/ConflictTraceAssembler
 */
import ConflictAnchor from "../../types/conflict/ConflictAnchor.js";
import ConflictParticipant from "../../types/conflict/ConflictParticipant.js";
import ConflictReceiptRef from "../../types/conflict/ConflictReceiptRef.js";
import ConflictTrace from "../../types/conflict/ConflictTrace.js";
import ConflictWinner from "../../types/conflict/ConflictWinner.js";
import { compareStrings } from "../../types/conflict/validation.js";
import { inferCausalRelation } from "./ConflictCandidateCollector.js";
import { CONFLICT_ANALYSIS_VERSION } from "./ConflictFrameLoader.js";
// ── Grouping ────────────────────────────────────────────────────────
function candidateGroupKey(candidate) {
    return [
        candidate.kind,
        candidate.target.targetDigest,
        new ConflictAnchor({
            patchSha: candidate.winner.patchSha,
            writerId: candidate.winner.writerId,
            lamport: candidate.winner.lamport,
            opIndex: candidate.winner.opIndex,
        }).toString(),
        candidate.resolution.reducerId,
        candidate.resolution.basis.code,
        candidate.resolution.winnerMode,
    ].join('|');
}
/**
 * Groups conflict candidates by their deterministic group key.
 */
export function groupCandidates(candidates) {
    const grouped = new Map();
    for (const candidate of candidates) {
        const key = candidateGroupKey(candidate);
        if (!grouped.has(key)) {
            grouped.set(key, {
                target: candidate.target,
                kind: candidate.kind,
                winner: candidate.winner,
                losers: [],
                resolution: candidate.resolution,
                noteCodes: new Set(),
            });
        }
        const group = grouped.get(key);
        group.losers.push(candidate.loser);
        for (const code of candidate.noteCodes) {
            group.noteCodes.add(code);
        }
    }
    return grouped;
}
// ── Winner/loser building ───────────────────────────────────────────
function buildLosers(group, evidence) {
    return group.losers
        .map((loser) => ConflictParticipant.fromRecord({ winner: group.winner, loser, kind: group.kind, evidence, inferCausalRelation: (w, l) => inferCausalRelation(w, l) }))
        .sort((a, b) => ConflictAnchor.compare(a.anchor, b.anchor));
}
// ── Trace building ──────────────────────────────────────────────────
function buildReceiptRef(record) {
    return new ConflictReceiptRef({
        patchSha: record.patchSha,
        lamport: record.lamport,
        opIndex: record.receiptOpIndex,
    });
}
function buildTraceEvidence(group, evidence) {
    return {
        level: evidence,
        patchRefs: [...new Set([group.winner.patchSha, ...group.losers.map((loser) => loser.patchSha)])].sort(compareStrings),
        receiptRefs: [buildReceiptRef(group.winner), ...group.losers.map(buildReceiptRef)].sort((a, b) => ConflictReceiptRef.compare(a, b)),
    };
}
function buildWhyFingerprintInput(group, losers) {
    return {
        targetDigest: group.target.targetDigest,
        kind: group.kind,
        reducerId: group.resolution.reducerId,
        basis: group.resolution.basis.code,
        winnerEffectDigest: group.winner.effectDigest,
        loserEffectDigests: losers.map((loser) => loser.effectDigest).sort(compareStrings),
    };
}
function buildConflictIdInput({ group, winner, losers, resolvedCoordinate, }) {
    return {
        analysisVersion: CONFLICT_ANALYSIS_VERSION,
        resolvedCoordinate,
        kind: group.kind,
        targetDigest: group.target.targetDigest,
        reducerId: group.resolution.reducerId,
        winnerAnchor: winner.anchor.toString(),
        loserAnchors: losers.map((loser) => loser.anchor.toString()),
    };
}
async function buildConflictTrace(context, { group, evidence, resolvedCoordinate, }) {
    const winner = ConflictWinner.fromRecord(group.winner);
    const losers = buildLosers(group, evidence);
    const whyFingerprint = await context.hash(buildWhyFingerprintInput(group, losers));
    const conflictId = await context.hash(buildConflictIdInput({ group, winner, losers, resolvedCoordinate }));
    const classificationNotes = evidence === 'full' ? [...group.noteCodes].sort(compareStrings) : undefined;
    return new ConflictTrace({
        conflictId,
        kind: group.kind,
        target: group.target,
        winner,
        losers,
        resolution: group.resolution,
        whyFingerprint,
        ...(classificationNotes !== undefined ? { classificationNotes } : {}),
        evidence: buildTraceEvidence(group, evidence),
    });
}
/**
 * Transforms grouped conflicts into sorted, finalized ConflictTrace records.
 */
export async function buildConflictTraces(context, { grouped, evidence, resolvedCoordinate, }) {
    const traces = [];
    for (const group of grouped) {
        traces.push(await buildConflictTrace(context, { group, evidence, resolvedCoordinate }));
    }
    traces.sort((a, b) => ConflictTrace.compare(a, b));
    return traces;
}
// ── Filtering ───────────────────────────────────────────────────────
/**
 * Filters an array of conflict traces against analysis options.
 */
export function filterTraces(traces, request) {
    return traces.filter((trace) => request.matchesTrace(trace));
}
// ── Snapshot hashing ────────────────────────────────────────────────
function diagnosticCodes(diagnostics) {
    return diagnostics.map((d) => d.code).sort(compareStrings);
}
/**
 * Computes a snapshot hash over the entire analysis result.
 */
export async function buildAnalysisSnapshotHash(context, { resolvedCoordinate, request, truncated, diagnostics, traces, }) {
    return await context.hash({
        analysisVersion: CONFLICT_ANALYSIS_VERSION,
        resolvedCoordinate,
        filters: request.toSnapshotFilterRecord(),
        truncation: truncated,
        conflictIds: traces.map((t) => t.conflictId).sort(compareStrings),
        diagnosticCodes: diagnosticCodes(diagnostics),
    });
}
/**
 * Computes a snapshot hash for an analysis that found zero conflicts.
 */
export async function buildEmptySnapshotHash(context, { resolvedCoordinate, request, }) {
    return await context.hash({
        analysisVersion: CONFLICT_ANALYSIS_VERSION,
        resolvedCoordinate,
        filters: request.toSnapshotFilterRecord(),
        truncation: false,
        conflictIds: [],
        diagnosticCodes: [],
    });
}
