/**
 * SyncPayloadSchema -- Zod schemas for sync protocol messages.
 *
 * Validates both shape and resource limits for sync requests and responses
 * at the trust boundary (HTTP ingress/egress). Prevents malformed or
 * oversized payloads from reaching the CRDT merge engine.
 *
 * @module domain/services/sync/SyncPayloadSchema
 * @see B64 -- Sync ingress payload validation
 */
import z from 'zod';
export const DEFAULT_LIMITS = Object.freeze({
    maxWritersInFrontier: 10_000,
    maxPatches: 100_000,
    maxOpsPerPatch: 50_000,
    maxStringBytes: 4096,
    maxBlobBytes: 16 * 1024 * 1024,
});
// ── Shared Primitives ───────────────────────────────────────────────────────
function boundedString(maxBytes) {
    return z.string().max(maxBytes);
}
// ── Frontier Schema ─────────────────────────────────────────────────────────
function mapToStringRecord(map) {
    const obj = {};
    for (const [k, v] of map) {
        if (typeof k !== 'string' || typeof v !== 'string') {
            return null;
        }
        obj[k] = v;
    }
    return obj;
}
function isPlainObject(value) {
    return value !== null && value !== undefined && typeof value === 'object' && !Array.isArray(value);
}
/**
 * Normalizes a frontier value that may be a Map (cbor-x decodes maps)
 * or a plain object into a validated plain object.
 */
export function normalizeFrontier(value) {
    if (value instanceof Map) {
        return mapToStringRecord(value);
    }
    if (isPlainObject(value)) {
        return value;
    }
    return null;
}
function frontierSchema(maxWriters) {
    return z.record(boundedString(256), z.string()).refine((obj) => Object.keys(obj).length <= maxWriters, (obj) => ({ message: `Frontier exceeds max writers: ${Object.keys(obj).length} > ${maxWriters}` }));
}
// ── Op Schema ───────────────────────────────────────────────────────────────
const opSchema = z.object({
    type: z.string(),
}).passthrough();
// ── Patch Schema ────────────────────────────────────────────────────────────
function patchSchema(limits) {
    return z.object({
        schema: z.number().int().min(1).optional(),
        writer: boundedString(limits.maxStringBytes).optional(),
        lamport: z.number().int().min(0).optional(),
        ops: z.array(opSchema).max(limits.maxOpsPerPatch),
        context: z.unknown().optional(), // nosemgrep: ts-no-unknown-outside-adapters -- 0025B
    }).passthrough();
}
function patchEntrySchema(limits) {
    return z.object({
        writerId: boundedString(limits.maxStringBytes),
        sha: z.string(),
        patch: patchSchema(limits),
    });
}
function syncRequestPageSchema(limits) {
    return z.object({
        maxPatches: z.number().int().min(1).max(limits.maxPatches),
        cursor: z.string().min(1).nullable().optional(),
    }).strict();
}
function syncResponsePageSchema() {
    return z.object({
        maxPatches: z.number().int().min(1).nullable(),
        cursor: z.string().min(1).nullable(),
        hasMore: z.boolean(),
        returnedPatches: z.number().int().min(0),
    }).strict();
}
function syncResponseMetricsSchema() {
    return z.object({
        patchCount: z.number().int().min(0),
        skippedWriterCount: z.number().int().min(0),
        estimatedPayloadBytes: z.number().int().min(0),
        latencyMs: z.number().min(0).nullable(),
    }).strict();
}
// ── Sync Request Schema ─────────────────────────────────────────────────────
export function createSyncRequestSchema(limits = DEFAULT_LIMITS) {
    return z.object({
        type: z.literal('sync-request'),
        frontier: frontierSchema(limits.maxWritersInFrontier),
        page: syncRequestPageSchema(limits).optional(),
    }).strict();
}
const SyncRequestSchema = createSyncRequestSchema();
// ── Sync Response Schema ────────────────────────────────────────────────────
export function createSyncResponseSchema(limits = DEFAULT_LIMITS) {
    return z.object({
        type: z.literal('sync-response'),
        frontier: frontierSchema(limits.maxWritersInFrontier),
        patches: z.array(patchEntrySchema(limits)).max(limits.maxPatches),
        page: syncResponsePageSchema().optional(),
        metrics: syncResponseMetricsSchema().optional(),
    }).passthrough();
}
const SyncResponseSchema = createSyncResponseSchema();
function normalizePayloadFrontier(payload) {
    if (!isPlainObject(payload)) {
        return null;
    }
    const p = payload; // nosemgrep: ts-no-record-string-unknown-outside-adapters -- 0025B; nosemgrep: ts-no-unknown-outside-adapters -- 0025B
    if (!(p['frontier'] instanceof Map)) {
        return null;
    }
    const normalized = normalizeFrontier(p['frontier']);
    if (normalized === null) {
        return 'Invalid frontier: Map contains non-string entries';
    }
    p['frontier'] = normalized;
    return null;
}
/**
 * Validates a sync request payload. Returns the parsed value or throws.
 */
export function validateSyncRequest(payload, // nosemgrep: ts-no-unknown-outside-adapters -- 0025B
limits = DEFAULT_LIMITS) {
    const frontierErr = normalizePayloadFrontier(payload);
    if (frontierErr !== null) {
        return { ok: false, error: frontierErr };
    }
    const schema = limits === DEFAULT_LIMITS ? SyncRequestSchema : createSyncRequestSchema(limits);
    const result = schema.safeParse(payload);
    if (!result.success) {
        return { ok: false, error: result.error.message };
    }
    return { ok: true, value: result.data };
}
/**
 * Validates a sync response payload. Returns the parsed value or an error.
 */
export function validateSyncResponse(payload, // nosemgrep: ts-no-unknown-outside-adapters -- 0025B
limits = DEFAULT_LIMITS) {
    const frontierErr = normalizePayloadFrontier(payload);
    if (frontierErr !== null) {
        return { ok: false, error: frontierErr };
    }
    const schema = limits === DEFAULT_LIMITS ? SyncResponseSchema : createSyncResponseSchema(limits);
    const result = schema.safeParse(payload);
    if (!result.success) {
        return { ok: false, error: result.error.message };
    }
    return { ok: true, value: result.data };
}
