var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
import { DwnInterfaceName, DwnMethodName, Encoder, Message, } from '@enbox/dwn-sdk-js';
import { DwnInterface } from './types/dwn.js';
import { DwnRpcError } from '@enbox/dwn-clients';
import { isRecordsWrite } from './utils.js';
import { toMessagesPermissionGrantIds } from './sync-permission-grants.js';
import { dependencyKey, hasTerminalDependency, isTenantProtocolConfig, missingDependencyDetail, newestProtocolConfig, resolveDelegatePermissionGrantId, } from './sync-fetch-helpers.js';
import { getRoleKey, orderMessagesForAdmission } from './sync-admission-order.js';
/** Maximum data size (in bytes) to buffer in memory for retry. Larger payloads are re-fetched. */
const MAX_BUFFER_SIZE = 1048576; // 1 MB
// Match the pull-side admission budget. Replication apply reports bounded,
// structured missing dependencies, while this cap prevents remote cycles from
// turning into unbounded local query/apply loops.
const MAX_PUSH_ADMISSION_PASSES = 128;
/** Raised when an in-flight pull is cancelled before local apply can continue. */
export class SyncPullAbortedError extends Error {
    constructor() {
        super('Sync pull aborted because the sync target is no longer current.');
        this.name = 'SyncPullAbortedError';
    }
}
export class SyncDataSizeLimitExceededError extends Error {
    constructor(dataSize) {
        super(`SyncEngineLevel: RecordsWrite data exceeded descriptor dataSize ${dataSize}.`);
        this.name = 'SyncDataSizeLimitExceededError';
    }
}
function assertShouldContinue(shouldContinue) {
    if ((shouldContinue === null || shouldContinue === void 0 ? void 0 : shouldContinue()) === false) {
        throw new SyncPullAbortedError();
    }
}
/**
 * Helper to get the CID of a message for logging purposes.
 */
export function getMessageCid(message) {
    return __awaiter(this, void 0, void 0, function* () {
        try {
            return yield Message.getCid(message);
        }
        catch (_a) {
            return 'unknown';
        }
    });
}
function dataStreamFromBytes(bytes) {
    return new ReadableStream({
        start(controller) {
            controller.enqueue(bytes);
            controller.close();
        }
    });
}
export function capRecordsWriteDataStream(message, dataStream) {
    if (!isRecordsWriteMessage(message)) {
        return dataStream;
    }
    const dataSize = message.descriptor.dataSize;
    if (typeof dataSize !== 'number') {
        return dataStream;
    }
    return capDataStream(dataStream, dataSize);
}
function capDataStream(dataStream, maxBytes) {
    let totalBytes = 0;
    return dataStream.pipeThrough(new TransformStream({
        transform(chunk, controller) {
            totalBytes += chunk.byteLength;
            if (totalBytes > maxBytes) {
                throw new SyncDataSizeLimitExceededError(maxBytes);
            }
            controller.enqueue(chunk);
        },
    }));
}
/**
 * Buffers small data streams into `Uint8Array` so they can be replayed on retry.
 * Streams larger than `MAX_BUFFER_SIZE` are left as-is (will be re-fetched on retry).
 */
function bufferSmallStreams(entries, shouldContinue) {
    return __awaiter(this, void 0, void 0, function* () {
        for (const entry of entries) {
            assertShouldContinue(shouldContinue);
            if (!shouldReadStreamIntoBuffer(entry)) {
                continue;
            }
            const buffer = yield bufferDataStream(entry, shouldContinue);
            entry.bufferedData = buffer;
            // Create a fresh ReadableStream from the buffer for the first processing attempt.
            entry.dataStream = dataStreamFromBytes(buffer);
            assertShouldContinue(shouldContinue);
        }
    });
}
function shouldReadStreamIntoBuffer(entry) {
    return entry.dataStream !== undefined &&
        entry.bufferedData === undefined &&
        shouldBufferDataStream(entry);
}
function bufferDataStream(entry, shouldContinue) {
    return __awaiter(this, void 0, void 0, function* () {
        const chunks = [];
        let totalSize = 0;
        const reader = entry.dataStream.getReader();
        try {
            for (;;) {
                const { done, value } = yield reader.read();
                if (done) {
                    break;
                }
                assertShouldContinue(shouldContinue);
                totalSize += value.byteLength;
                if (totalSize > MAX_BUFFER_SIZE) {
                    throw new Error('SyncEngineLevel: unexpected large stream while buffering push data.');
                }
                chunks.push(value);
            }
        }
        finally {
            reader.releaseLock();
        }
        return concatChunks(chunks, totalSize);
    });
}
function concatChunks(chunks, totalSize) {
    const buffer = new Uint8Array(totalSize);
    let offset = 0;
    for (const chunk of chunks) {
        buffer.set(chunk, offset);
        offset += chunk.byteLength;
    }
    return buffer;
}
function shouldBufferDataStream(entry) {
    if (!isRecordsWriteMessage(entry.message)) {
        return true;
    }
    const dataSize = entry.message.descriptor.dataSize;
    return typeof dataSize === 'number' && dataSize <= MAX_BUFFER_SIZE;
}
function buildMessageFeedParams({ filters, cursor, limit, cidsOnly, permissionGrantIds, }) {
    return {
        filters,
        cursor,
        limit,
        cidsOnly,
        permissionGrantIds: toMessagesPermissionGrantIds(permissionGrantIds)
    };
}
/**
 * Queries a remote DWN's durable message feed using MessagesQuery.
 */
export function queryRemoteMessageFeed(_a) {
    return __awaiter(this, arguments, void 0, function* ({ did, dwnUrl, delegateDid, permissionGrantIds, filters, cursor, limit, cidsOnly, agent, }) {
        const messagesQuery = yield agent.processDwnRequest({
            store: false,
            author: did,
            target: did,
            messageType: DwnInterface.MessagesQuery,
            granteeDid: delegateDid,
            messageParams: buildMessageFeedParams({ filters, cursor, limit, cidsOnly, permissionGrantIds })
        });
        return yield agent.rpc.sendDwnRequest({
            dwnUrl,
            targetDid: did,
            message: messagesQuery.message,
        });
    });
}
/**
 * Queries the local DWN's durable message feed using MessagesQuery.
 */
export function queryLocalMessageFeed(_a) {
    return __awaiter(this, arguments, void 0, function* ({ did, delegateDid, permissionGrantIds, filters, cursor, limit, cidsOnly, agent, }) {
        const { reply } = yield agent.dwn.processRequest({
            author: did,
            target: did,
            messageType: DwnInterface.MessagesQuery,
            granteeDid: delegateDid,
            messageParams: buildMessageFeedParams({ filters, cursor, limit, cidsOnly, permissionGrantIds })
        });
        return reply;
    });
}
/**
 * Fetches messages from a remote DWN by their CIDs using MessagesRead.
 */
export function fetchRemoteMessages(_a) {
    return __awaiter(this, arguments, void 0, function* ({ did, dwnUrl, delegateDid, permissionGrantIds, messageCids, agent }) {
        const results = [];
        // Fetch messages in parallel with bounded concurrency.  Keep this low
        // to avoid bursting through the remote server's rate limits during sync.
        const CONCURRENCY = 4;
        let cursor = 0;
        while (cursor < messageCids.length) {
            const batch = messageCids.slice(cursor, cursor + CONCURRENCY);
            cursor += CONCURRENCY;
            const batchResults = yield Promise.all(batch.map((messageCid) => __awaiter(this, void 0, void 0, function* () {
                var _a, _b;
                const messagesRead = yield agent.processDwnRequest({
                    store: false,
                    author: did,
                    target: did,
                    messageType: DwnInterface.MessagesRead,
                    granteeDid: delegateDid,
                    messageParams: { messageCid, permissionGrantIds: toMessagesPermissionGrantIds(permissionGrantIds) }
                });
                let reply;
                try {
                    reply = (yield agent.rpc.sendDwnRequest({
                        dwnUrl,
                        targetDid: did,
                        message: messagesRead.message,
                    }));
                }
                catch (error) {
                    console.error(`SyncEngineLevel: pull - failed to read ${messageCid} from ${dwnUrl}:`, (_a = error.message) !== null && _a !== void 0 ? _a : error);
                    return undefined;
                }
                if (reply.status.code !== 200 || !((_b = reply.entry) === null || _b === void 0 ? void 0 : _b.message)) {
                    return undefined;
                }
                const replyEntry = reply.entry;
                let dataStream;
                if (isRecordsWrite(replyEntry) && replyEntry.data) {
                    dataStream = capRecordsWriteDataStream(replyEntry.message, replyEntry.data);
                }
                return { message: replyEntry.message, dataStream };
            })));
            for (const result of batchResults) {
                if (result) {
                    results.push(result);
                }
            }
        }
        return results;
    });
}
/**
 * Reads local roots and pushes them through the remote DWN replication entry.
 * The remote DWN is the dependency authority: `Incomplete` results request the
 * exact local dependency refs to fetch and retry.
 *
 * Returns a {@link PushResult} with per-CID outcome tracking instead of throwing
 * on the first failure. Callers use failures to retry transient push problems,
 * dead-letter `Invalid` remote rejections, or mark links for reconciliation.
 */
export function pushMessages(_a) {
    return __awaiter(this, arguments, void 0, function* ({ did, dwnUrl, delegateDid, permissionGrantIds, messageCids, agent, permissionsApi }) {
        const context = new RemoteApplyPushContext({
            did,
            dwnUrl,
            delegateDid,
            permissionGrantIds,
            agent,
            permissionsApi,
        });
        return context.push([...new Set(messageCids)]);
    });
}
/**
 * Reads a message from the local DWN by its CID using MessagesRead.
 */
export function getLocalMessage(_a) {
    return __awaiter(this, arguments, void 0, function* ({ author, delegateDid, permissionGrantIds, messageCid, agent }) {
        const result = yield readLocalMessage({ author, delegateDid, permissionGrantIds, messageCid, agent });
        return result.kind === 'found' ? result.entry : undefined;
    });
}
function readLocalMessage(_a) {
    return __awaiter(this, arguments, void 0, function* ({ author, delegateDid, permissionGrantIds, messageCid, agent }) {
        const { reply } = yield agent.dwn.processRequest({
            author,
            target: author,
            messageType: DwnInterface.MessagesRead,
            granteeDid: delegateDid,
            messageParams: { messageCid, permissionGrantIds: toMessagesPermissionGrantIds(permissionGrantIds) }
        });
        if (reply.status.code !== 200 || !reply.entry) {
            return { kind: 'missing', localStatusCode: reply.status.code, detail: reply.status.detail };
        }
        const messageEntry = reply.entry;
        const result = {
            message: messageEntry.message
        };
        if (isRecordsWrite(messageEntry) && messageEntry.data) {
            result.dataStream = capRecordsWriteDataStream(messageEntry.message, messageEntry.data);
            result.dataStreamFactory = () => __awaiter(this, void 0, void 0, function* () {
                const refreshed = yield readLocalMessage({ author, delegateDid, permissionGrantIds, messageCid, agent });
                return refreshed.kind === 'found' ? refreshed.entry.dataStream : undefined;
            });
        }
        return { kind: 'found', entry: result };
    });
}
class RemoteApplyPushContext {
    constructor(deps) {
        this.deps = deps;
        this.entriesByCid = new Map();
        this.fetchedDependencyEntries = new Map();
        this.fetchedRefs = new Set();
    }
    push(rootCids) {
        return __awaiter(this, void 0, void 0, function* () {
            const succeeded = [];
            const failedByRoot = new Map();
            const rootEntries = [];
            for (const rootCid of rootCids) {
                const root = yield this.fetchMessageCid(rootCid);
                if (root.kind === 'failed') {
                    failedByRoot.set(rootCid, {
                        cid: rootCid,
                        detail: root.detail,
                    });
                    continue;
                }
                rootEntries.push(...root.entries);
            }
            for (const rootEntry of orderMessagesForAdmission(rootEntries)) {
                const rootCid = yield this.rememberEntry(rootEntry);
                if (failedByRoot.has(rootCid)) {
                    continue;
                }
                const outcome = yield this.pushRoot(rootCid, rootEntry);
                if (outcome.kind === 'succeeded') {
                    succeeded.push(outcome.cid);
                    failedByRoot.delete(outcome.cid);
                }
                else {
                    failedByRoot.set(outcome.failure.cid, outcome.failure);
                }
            }
            return { succeeded, failed: [...failedByRoot.values()] };
        });
    }
    pushRoot(rootCid, rootEntry) {
        return __awaiter(this, void 0, void 0, function* () {
            let pending = [rootEntry];
            for (let pass = 0; pass < MAX_PUSH_ADMISSION_PASSES && pending.length > 0; pass++) {
                const retry = [];
                for (const entry of orderMessagesForAdmission(pending)) {
                    const result = yield this.pushEntry(rootCid, entry);
                    switch (result.kind) {
                        case 'applied':
                            break;
                        case 'retry':
                            retry.push(...result.entries);
                            break;
                        case 'failed':
                            return { kind: 'failed', failure: result.failure };
                    }
                }
                pending = yield dedupeEntries(retry);
            }
            return pending.length === 0
                ? { kind: 'succeeded', cid: rootCid }
                : {
                    kind: 'failed',
                    failure: { cid: rootCid, detail: 'remote dependency apply pass budget exhausted' },
                };
        });
    }
    pushEntry(rootCid, entry) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            const cid = yield this.rememberEntry(entry);
            try {
                yield bufferSmallStreams([entry]);
            }
            catch (error) {
                const detail = (_a = error.message) !== null && _a !== void 0 ? _a : String(error);
                console.error(`SyncEngineLevel: push error for ${cid}: ${detail}`);
                if (error instanceof SyncDataSizeLimitExceededError) {
                    return {
                        kind: 'failed',
                        failure: this.terminalFailure(rootCid, cid, detail, { kind: 'Invalid', reason: detail }),
                    };
                }
                return { kind: 'failed', failure: this.retryableFailure(rootCid, cid, detail) };
            }
            let result;
            try {
                result = yield this.deps.agent.rpc.applyReplicatedMessage({
                    dwnUrl: this.deps.dwnUrl,
                    targetDid: this.deps.did,
                    data: yield pushData(entry),
                    message: entry.message,
                });
            }
            catch (error) {
                const detail = (_b = error.message) !== null && _b !== void 0 ? _b : String(error);
                console.error(`SyncEngineLevel: push error for ${cid}: ${detail}`);
                if (error instanceof SyncDataSizeLimitExceededError) {
                    return { kind: 'failed', failure: this.terminalFailure(rootCid, cid, detail, { kind: 'Invalid', reason: detail }) };
                }
                if (error instanceof DwnRpcError && error.terminal) {
                    return { kind: 'failed', failure: this.terminalFailure(rootCid, cid, detail, { kind: 'Invalid', reason: detail }) };
                }
                return { kind: 'failed', failure: this.retryableFailure(rootCid, cid, detail) };
            }
            return this.pushResultFromApply(rootCid, cid, entry, result);
        });
    }
    pushResultFromApply(rootCid, cid, entry, result) {
        return __awaiter(this, void 0, void 0, function* () {
            switch (result.kind) {
                case 'Applied':
                case 'Duplicate':
                case 'Superseded':
                    return { kind: 'applied', cid };
                case 'Deferred':
                    return {
                        kind: 'failed',
                        failure: this.retryableFailure(rootCid, cid, result.reason, result),
                    };
                case 'Invalid':
                    return {
                        kind: 'failed',
                        failure: this.terminalFailure(rootCid, cid, result.reason, result),
                    };
                case 'Incomplete':
                    return this.pushResultFromMissingDependencies(rootCid, cid, entry, result.missing);
                default:
                    return unreachable(result);
            }
        });
    }
    pushResultFromMissingDependencies(rootCid, cid, entry, missing) {
        return __awaiter(this, void 0, void 0, function* () {
            if (hasTerminalDependency(missing)) {
                return {
                    kind: 'failed',
                    failure: this.terminalFailure(rootCid, cid, missingDependencyDetail(missing), { kind: 'Incomplete', missing }),
                };
            }
            const dependencies = yield this.fetchMissingDependencies(missing);
            if (dependencies.kind === 'failed') {
                return {
                    kind: 'failed',
                    failure: this.retryableFailure(rootCid, cid, dependencies.detail),
                };
            }
            if (dependencies.entries.length === 0) {
                return {
                    kind: 'failed',
                    failure: this.retryableFailure(rootCid, cid, missingDependencyDetail(missing)),
                };
            }
            return { kind: 'retry', entries: [...dependencies.entries, entry] };
        });
    }
    terminalFailure(rootCid, cid, detail, result) {
        return {
            cid: rootCid,
            kind: result.kind,
            terminal: true,
            detail: cid === rootCid ? detail : `dependency ${cid} failed before root push: ${detail}`,
        };
    }
    retryableFailure(rootCid, cid, detail, result) {
        return Object.assign(Object.assign(Object.assign(Object.assign({ cid: rootCid }, (cid === rootCid ? {} : { dependencyCid: cid })), (result === undefined ? {} : { kind: result.kind, reason: result.reason })), ((result === null || result === void 0 ? void 0 : result.reason) === 'tenant-inactive' ? { tenantInactive: true } : {})), { detail: cid === rootCid ? detail : `dependency ${cid} failed before root push: ${detail}` });
    }
    fetchMissingDependencies(refs) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            const fetched = [];
            for (const ref of refs) {
                const key = dependencyKey(ref);
                if (this.fetchedRefs.has(key)) {
                    fetched.push(...((_a = this.fetchedDependencyEntries.get(key)) !== null && _a !== void 0 ? _a : []));
                    continue;
                }
                const result = yield this.fetchDependency(ref);
                if (result.kind === 'failed') {
                    return result;
                }
                this.fetchedRefs.add(key);
                this.fetchedDependencyEntries.set(key, result.entries);
                fetched.push(...result.entries);
            }
            return { kind: 'fetched', entries: fetched };
        });
    }
    fetchDependency(ref) {
        return __awaiter(this, void 0, void 0, function* () {
            if (ref.messageCid !== undefined) {
                return this.fetchMessageCid(ref.messageCid);
            }
            switch (ref.type) {
                case 'Protocol':
                    return this.fetchProtocolConfig(ref.protocol);
                case 'InitialWrite':
                    return this.fetchRecordsByRecordId(ref.recordId, ref.protocol);
                case 'Parent':
                    return this.fetchRecordsByRecordId(ref.recordId, ref.protocol);
                case 'Ancestor':
                case 'CrossProtocolRef':
                    return this.fetchRecordsByRecordId(ref.recordId, ref.protocol);
                case 'Role':
                    return this.fetchRoleRecord(ref);
                case 'Grant':
                    return this.fetchRecordsByRecordId(ref.permissionGrantId);
                case 'RecordData':
                    return this.fetchRecordData(ref);
                case 'KeyDelivery':
                    // dependencyRefFromStatus() does not currently emit KeyDelivery for push.
                    // Key delivery is installed proactively with encrypted protocols, so keep
                    // this documented no-op instead of building an unexercised fetch path.
                    return { kind: 'fetched', entries: [] };
                default:
                    return unreachable(ref);
            }
        });
    }
    fetchMessageCid(messageCid) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            const result = yield readLocalMessage({
                author: this.deps.did,
                delegateDid: this.deps.delegateDid,
                permissionGrantIds: this.deps.permissionGrantIds,
                messageCid,
                agent: this.deps.agent,
            });
            if (result.kind === 'missing') {
                return {
                    kind: 'failed',
                    detail: `local dependency message ${messageCid} not found (${(_a = result.localStatusCode) !== null && _a !== void 0 ? _a : 'unknown'} ${(_b = result.detail) !== null && _b !== void 0 ? _b : ''})`,
                };
            }
            yield this.rememberEntry(result.entry);
            return { kind: 'fetched', entries: [result.entry] };
        });
    }
    fetchProtocolConfig(protocol) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            const permissionGrantId = yield resolveDelegatePermissionGrantId(this.deps, DwnInterface.ProtocolsQuery, protocol);
            const granteeDid = permissionGrantId === undefined ? undefined : this.deps.delegateDid;
            const { reply } = yield this.deps.agent.dwn.processRequest({
                author: (_a = this.deps.delegateDid) !== null && _a !== void 0 ? _a : this.deps.did,
                granteeDid,
                messageParams: Object.assign({ filter: { protocol } }, (permissionGrantId === undefined ? {} : { permissionGrantId })),
                messageType: DwnInterface.ProtocolsQuery,
                store: false,
                target: this.deps.did,
            });
            const protocolsReply = reply;
            if (protocolsReply.status.code !== 200 || protocolsReply.entries === undefined) {
                return {
                    kind: 'failed',
                    detail: `local protocol query failed for ${protocol}: ${protocolsReply.status.code} ${(_b = protocolsReply.status.detail) !== null && _b !== void 0 ? _b : ''}`,
                };
            }
            const config = newestProtocolConfig(protocolsReply.entries.filter(isTenantProtocolConfig(this.deps.did, protocol)));
            const entries = config === undefined ? [] : [{ message: config }];
            yield this.rememberEntries(entries);
            return { kind: 'fetched', entries };
        });
    }
    fetchRecordsByRecordId(recordId, protocol) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            const permissionGrantId = protocol === undefined
                ? undefined
                : yield resolveDelegatePermissionGrantId(this.deps, DwnInterface.RecordsQuery, protocol);
            const granteeDid = permissionGrantId === undefined ? undefined : this.deps.delegateDid;
            const { reply } = yield this.deps.agent.dwn.processRequest({
                author: (_a = this.deps.delegateDid) !== null && _a !== void 0 ? _a : this.deps.did,
                granteeDid,
                messageParams: Object.assign({ filter: Object.assign({ recordId }, (protocol === undefined ? {} : { protocol })) }, (permissionGrantId === undefined ? {} : { permissionGrantId })),
                messageType: DwnInterface.RecordsQuery,
                store: false,
                target: this.deps.did,
            });
            const recordsReply = reply;
            if (recordsReply.status.code !== 200 || recordsReply.entries === undefined) {
                return {
                    kind: 'failed',
                    detail: `local records query failed for ${recordId}: ${recordsReply.status.code} ${(_b = recordsReply.status.detail) !== null && _b !== void 0 ? _b : ''}`,
                };
            }
            return { kind: 'fetched', entries: yield this.entriesFromRecordsQueryEntries(recordsReply.entries) };
        });
    }
    fetchRoleRecord(ref) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            const permissionGrantId = yield resolveDelegatePermissionGrantId(this.deps, DwnInterface.RecordsQuery, ref.protocol);
            const granteeDid = permissionGrantId === undefined ? undefined : this.deps.delegateDid;
            const key = getRoleKey(ref.protocol, ref.protocolPath, ref.recipient, ref.contextPrefix);
            const { reply } = yield this.deps.agent.dwn.processRequest({
                author: (_a = this.deps.delegateDid) !== null && _a !== void 0 ? _a : this.deps.did,
                granteeDid,
                messageParams: Object.assign({ filter: Object.assign({ protocol: ref.protocol, protocolPath: ref.protocolPath, recipient: ref.recipient }, (ref.contextPrefix === undefined ? {} : { contextId: ref.contextPrefix })) }, (permissionGrantId === undefined ? {} : { permissionGrantId })),
                messageType: DwnInterface.RecordsQuery,
                store: false,
                target: this.deps.did,
            });
            const recordsReply = reply;
            if (recordsReply.status.code !== 200 || recordsReply.entries === undefined) {
                return {
                    kind: 'failed',
                    detail: `local role query failed for ${key}: ${recordsReply.status.code} ${(_b = recordsReply.status.detail) !== null && _b !== void 0 ? _b : ''}`,
                };
            }
            return { kind: 'fetched', entries: yield this.entriesFromRecordsQueryEntries(recordsReply.entries) };
        });
    }
    fetchRecordData(ref) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b, _c;
            const permissionGrantId = ref.protocol === undefined
                ? undefined
                : yield resolveDelegatePermissionGrantId(this.deps, DwnInterface.RecordsRead, ref.protocol);
            const granteeDid = permissionGrantId === undefined ? undefined : this.deps.delegateDid;
            const { reply } = yield this.deps.agent.dwn.processRequest({
                author: (_a = this.deps.delegateDid) !== null && _a !== void 0 ? _a : this.deps.did,
                granteeDid,
                messageParams: Object.assign({ filter: { recordId: ref.recordId } }, (permissionGrantId === undefined ? {} : { permissionGrantId })),
                messageType: DwnInterface.RecordsRead,
                store: false,
                target: this.deps.did,
            });
            const recordsReply = reply;
            if (recordsReply.status.code !== 200 || ((_b = recordsReply.entry) === null || _b === void 0 ? void 0 : _b.recordsWrite) === undefined || recordsReply.entry.data === undefined) {
                return {
                    kind: 'failed',
                    detail: `local record data read failed for ${ref.recordId}: ${recordsReply.status.code} ${(_c = recordsReply.status.detail) !== null && _c !== void 0 ? _c : ''}`,
                };
            }
            const recordsWrite = recordsReply.entry.recordsWrite;
            if (recordsWrite.descriptor.dataCid !== ref.dataCid) {
                return { kind: 'fetched', entries: [] };
            }
            const entry = {
                message: recordsWrite,
                dataStream: capRecordsWriteDataStream(recordsWrite, recordsReply.entry.data),
                dataStreamFactory: () => __awaiter(this, void 0, void 0, function* () {
                    var _a;
                    const refreshed = yield this.fetchRecordData(ref);
                    return refreshed.kind === 'fetched' ? (_a = refreshed.entries[0]) === null || _a === void 0 ? void 0 : _a.dataStream : undefined;
                }),
            };
            yield this.rememberEntry(entry);
            return { kind: 'fetched', entries: [entry] };
        });
    }
    entriesFromRecordsQueryEntries(recordsQueryEntries) {
        return __awaiter(this, void 0, void 0, function* () {
            const entries = [];
            for (const recordEntry of recordsQueryEntries) {
                const { encodedData, initialWrite } = recordEntry, message = __rest(recordEntry, ["encodedData", "initialWrite"]);
                if (initialWrite !== undefined) {
                    entries.push(yield this.entryForRecordsQueryMessage(initialWrite));
                }
                entries.push(yield this.entryForRecordsQueryMessage(message, encodedData));
            }
            const dedupedEntries = yield dedupeEntries(entries);
            yield this.rememberEntries(dedupedEntries);
            return dedupedEntries;
        });
    }
    entryForRecordsQueryMessage(message, encodedData) {
        return __awaiter(this, void 0, void 0, function* () {
            const entry = { message };
            if (encodedData !== undefined) {
                entry.bufferedData = Encoder.base64UrlToBytes(encodedData);
                return entry;
            }
            if (isRecordsWriteMessage(message) && message.descriptor.dataCid !== undefined) {
                const messageCid = yield getMessageCid(message);
                const hydrated = yield getLocalMessage({
                    author: this.deps.did,
                    delegateDid: this.deps.delegateDid,
                    permissionGrantIds: this.deps.permissionGrantIds,
                    messageCid,
                    agent: this.deps.agent,
                });
                if (hydrated !== undefined) {
                    return hydrated;
                }
            }
            return entry;
        });
    }
    rememberEntries(entries) {
        return __awaiter(this, void 0, void 0, function* () {
            for (const entry of entries) {
                yield this.rememberEntry(entry);
            }
        });
    }
    rememberEntry(entry) {
        return __awaiter(this, void 0, void 0, function* () {
            const cid = yield Message.getCid(entry.message);
            this.entriesByCid.set(cid, entry);
            return cid;
        });
    }
}
function pushData(entry) {
    return __awaiter(this, void 0, void 0, function* () {
        if (entry.bufferedData !== undefined) {
            return new Blob([entry.bufferedData], { type: 'application/octet-stream' });
        }
        if (entry.dataStream !== undefined && entry.dataStreamConsumed !== true) {
            entry.dataStreamConsumed = true;
            return entry.dataStream;
        }
        if (entry.dataStreamFactory !== undefined) {
            return entry.dataStreamFactory();
        }
        return entry.dataStream;
    });
}
function dedupeEntries(entries) {
    return __awaiter(this, void 0, void 0, function* () {
        const byCid = new Map();
        for (const entry of entries) {
            byCid.set(yield getMessageCid(entry.message), entry);
        }
        return [...byCid.values()];
    });
}
function isRecordsWriteMessage(message) {
    return message.descriptor.interface === DwnInterfaceName.Records &&
        message.descriptor.method === DwnMethodName.Write &&
        typeof message.recordId === 'string';
}
function unreachable(value) {
    throw new Error(`SyncEngineLevel: unreachable sync push branch ${JSON.stringify(value)}`);
}
//# sourceMappingURL=sync-messages.js.map