import { DiffCompleteness, FileChangeSetStatus, FileDecisionAction, FileDecisionScope, FileReviewBlockReason, } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
/**
 * Classify a {@link CapturedFileChange} by how completely it can be reviewed,
 * from the signals the runner authors onto the change (see the file-review
 * producer in the runner's `shared/filereview`).
 *
 * The mapping is derived from three proto signals, in order:
 *
 * 1. `diff_complete === true` → `"reviewable"`. The change set can only be
 *    `COMPLETE` when every file is complete, so a complete file is always
 *    reviewable.
 * 2. otherwise, a binary side (`before.is_binary` or `after.is_binary`) →
 *    `"binary"`. The runner marks a git-tracked file incomplete precisely
 *    because a side is binary (`diff_complete = !binary`); a CAS file carries
 *    `is_binary` on its blob ref. `is_binary` is preserved when a large body is
 *    offloaded from an inline value to a storage ref, so this holds regardless
 *    of offload. Binary is therefore derived here, never duplicated onto the
 *    block reason (doc 15).
 * 3. otherwise → `"unavailable"` with the honest cause from
 *    `blocked_reason`: `SECRET_WITHHELD → "secret"`, `SIZE_ELIDED → "size"`,
 *    else `"unknown"`. Before doc 15 this bucket was cause-agnostic (the wire
 *    could not prove secret-withheld vs size-dropped); the runner now records
 *    the cause at capture time, so the UI can say *why* rather than only *that*
 *    the diff is unavailable.
 */
export function fileReviewability(change) {
    if (change.diffComplete)
        return { kind: "reviewable" };
    if (change.before?.isBinary || change.after?.isBinary)
        return { kind: "binary" };
    return { kind: "unavailable", reason: blockReasonOf(change.blockedReason) };
}
/** Map the proto {@link FileReviewBlockReason} to the UI {@link FileBlockReason}. */
function blockReasonOf(reason) {
    switch (reason) {
        case FileReviewBlockReason.SECRET_WITHHELD:
            return "secret";
        case FileReviewBlockReason.SIZE_ELIDED:
            return "size";
        default:
            return "unknown";
    }
}
/**
 * Classify a {@link FileChangeSet} from its server-derived `diff_completeness`
 * rollup (the field exists precisely so clients need not re-fold the per-file
 * signals). The runner and the size backstop both derive that value from the
 * same rule the backend gate re-checks, so this classification, the per-file
 * {@link fileReviewability} fold, and the server gate agree by construction.
 */
export function changeSetReviewability(set) {
    switch (set.diffCompleteness) {
        case DiffCompleteness.COMPLETE:
            return "complete";
        case DiffCompleteness.BINARY_SUMMARY_ONLY:
            return "binary-only";
        default:
            return "blocked";
    }
}
/**
 * Each file's EFFECTIVE verdict for a settled set, folding the decisions the
 * way the runner reconcile does (most-specific-wins): the last CHANGE_SET
 * decision is the baseline for every file, and a later-or-earlier FILE decision
 * overrides it for that file (FILE > CHANGE_SET regardless of ledger order;
 * within a scope, last write wins).
 *
 * This is the fold for DISPLAYING history — a set decided via "Keep all"
 * correctly reads every file as kept. The interactive per-file selected state
 * deliberately uses the FILE-only fold instead (a CHANGE_SET decision cannot
 * coexist with an open review, so it never pre-selects anything there).
 */
export function deriveEffectiveVerdicts(set) {
    let bulk = null;
    const byFileId = new Map();
    for (const d of set.decisions) {
        if (d.scope === FileDecisionScope.CHANGE_SET)
            bulk = d.action;
        else if (d.scope === FileDecisionScope.FILE && d.fileChangeId) {
            byFileId.set(d.fileChangeId, d.action);
        }
    }
    const effective = new Map();
    for (const c of set.changes) {
        const verdict = byFileId.get(c.id) ?? bulk;
        if (verdict != null)
            effective.set(c.id, verdict);
    }
    return effective;
}
/**
 * The captured change a transcript row's file path refers to, or `null`. The
 * row's path may be absolute (harness tool args) while captured paths are
 * workspace-root-relative, so the match accepts an exact match or a
 * `/`-boundary suffix. Presentation-only — never a correlation key.
 */
export function changeForRowPath(set, rowPath) {
    if (!rowPath)
        return null;
    const normalized = rowPath.replace(/\\/g, "/");
    for (const c of set.changes) {
        const path = c.pathAfter || c.pathBefore;
        if (path && (normalized === path || normalized.endsWith(`/${path}`))) {
            return c;
        }
    }
    return null;
}
export function fileReviewRowState(set, rowPath) {
    if (!set)
        return null;
    switch (set.status) {
        case FileChangeSetStatus.AWAITING_REVIEW:
            return "pending";
        case FileChangeSetStatus.FAILED:
            return "failed";
        case FileChangeSetStatus.DECIDED:
        case FileChangeSetStatus.RECONCILED: {
            if (!rowPath)
                return null;
            const change = changeForRowPath(set, rowPath);
            if (!change)
                return null;
            const verdict = deriveEffectiveVerdicts(set).get(change.id);
            if (verdict === FileDecisionAction.APPROVE)
                return "kept";
            if (verdict === FileDecisionAction.REJECT)
                return "discarded";
            return null;
        }
        default:
            return null;
    }
}
//# sourceMappingURL=file-review-status.js.map