/**
 * Versioned sanitized sync projection contracts.
 *
 * Each domain emits a payload with:
 *   - `contractVersion`: bumped on shape change so the backend can fail safe.
 *   - `domain`: one of the bounded `SyncDomain` enum values.
 *   - `idempotencyKey`: `workspaceId|domain|rowId|contentHash` so the backend
 *     can replay safely and detect content-mutating updates.
 *   - `payload`: a small, sanitized snapshot. All strings are run through
 *     `isUnsafePublicString`; rows that fail are rejected upstream by the
 *     worker, never emitted.
 *
 * Contracts intentionally do NOT include raw provider IDs, paths, secrets,
 * matcher keys, or audit fragments. They are safe to log and to persist on
 * the hosted backend without leaking local internals.
 */
import { createHash } from "node:crypto";
import { isUnsafePublicString } from "./runProjectionContract.js";
export const SYNC_CONTRACT_VERSION = "v1";
function safeString(value) {
    if (value === null || value === undefined)
        return null;
    if (typeof value !== "string")
        return null;
    if (value.length > 256)
        return null;
    if (isUnsafePublicString(value))
        return null;
    return value;
}
export function buildSyncPayloadContentHash(payload) {
    const canonical = stableStringify(payload);
    return createHash("sha256").update(canonical).digest("hex").slice(0, 32);
}
function stableStringify(value) {
    if (value === null || typeof value !== "object") {
        return JSON.stringify(value);
    }
    if (Array.isArray(value)) {
        return `[${value.map((item) => stableStringify(item)).join(",")}]`;
    }
    const record = value;
    const entries = Object.keys(record)
        .filter((key) => record[key] !== undefined)
        .sort()
        .map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`);
    return `{${entries.join(",")}}`;
}
function envelope(domain, workspaceId, rowId, payload) {
    const hash = buildSyncPayloadContentHash(payload);
    return {
        contractVersion: SYNC_CONTRACT_VERSION,
        domain,
        workspaceId,
        rowId,
        idempotencyKey: `${workspaceId}|${domain}|${rowId}|${hash}`,
        payload,
    };
}
export function buildRunSync(row) {
    if (isUnsafePublicString(row.id) || isUnsafePublicString(row.workspaceId))
        return null;
    const title = safeString(row.title ?? null);
    return envelope("runs", row.workspaceId, row.id, {
        status: row.status,
        title,
        createdAt: row.createdAt,
    });
}
export function buildRuntimeEventSync(row, workspaceId) {
    if (isUnsafePublicString(row.id) || isUnsafePublicString(workspaceId))
        return null;
    if (!row.runId || isUnsafePublicString(row.runId))
        return null;
    return envelope("runtimeEvents", workspaceId, row.id, {
        runId: row.runId,
        type: row.type,
        tone: row.tone,
        occurredAt: row.occurredAt,
    });
}
export function buildProofEvidenceSync(row, workspaceId) {
    if (isUnsafePublicString(workspaceId) || isUnsafePublicString(row.runId))
        return null;
    return envelope("proofEvidence", workspaceId, row.runId, {
        runId: row.runId,
        changedFileCount: row.changedFiles.length,
        warningCount: row.warnings.length,
        updatedAt: row.updatedAt,
    });
}
export function buildPermissionActivitySync(row) {
    if (isUnsafePublicString(row.id) || isUnsafePublicString(row.workspaceId))
        return null;
    return envelope("permissionActivity", row.workspaceId, row.id, {
        kind: row.kind,
        occurredAt: row.at,
    });
}
export function buildRepositorySync(row) {
    if (isUnsafePublicString(row.id) || isUnsafePublicString(row.workspaceId))
        return null;
    const label = safeString(row.displayLabel);
    if (!label)
        return null;
    return envelope("repositories", row.workspaceId, row.id, {
        displayLabel: label,
        status: row.status,
        providerLabel: row.provider,
    });
}
export function buildOnboardingSync(row) {
    if (isUnsafePublicString(row.id) || isUnsafePublicString(row.workspaceId))
        return null;
    const label = safeString(row.displayLabel);
    if (!label)
        return null;
    return envelope("onboarding", row.workspaceId, row.id, {
        gate: row.gate,
        status: row.status,
        displayLabel: label,
    });
}
export function buildSettingsSync(row) {
    if (isUnsafePublicString(row.id) || isUnsafePublicString(row.workspaceId))
        return null;
    const label = safeString(row.displayLabel);
    if (!label)
        return null;
    return envelope("settings", row.workspaceId, row.id, {
        area: row.area,
        status: row.status,
        displayLabel: label,
    });
}
export function buildReviewQueueSync(row, latestDecision) {
    if (isUnsafePublicString(row.id) || isUnsafePublicString(row.workspaceId))
        return null;
    const headline = safeString(row.headline);
    if (!headline)
        return null;
    return envelope("reviewQueue", row.workspaceId, row.id, {
        status: row.status,
        headline,
        decision: latestDecision?.decision ?? null,
        decidedAt: latestDecision?.decidedAt ?? null,
    });
}
