import { getInvokedPermissionGrantIds } from './sync-permission-grants.js';
import { DwnInterfaceName, DwnMethodName, isCrossProtocolRef, Jws, Message, parseCrossProtocolRef, PermissionsProtocol } from '@enbox/dwn-sdk-js';
function isRecordsDescriptor(descriptor) {
    return descriptor.interface === DwnInterfaceName.Records;
}
function isRecordsWriteDescriptor(descriptor) {
    return descriptor.interface === DwnInterfaceName.Records && descriptor.method === DwnMethodName.Write;
}
function isRecordsDeleteDescriptor(descriptor) {
    return descriptor.interface === DwnInterfaceName.Records && descriptor.method === DwnMethodName.Delete;
}
function isProtocolsConfigureDescriptor(descriptor) {
    return descriptor.interface === DwnInterfaceName.Protocols && descriptor.method === DwnMethodName.Configure;
}
function getRecordId(message) {
    return message.recordId;
}
function getProtocolDefinition(message) {
    const { descriptor } = message;
    return isProtocolsConfigureDescriptor(descriptor) ? descriptor.definition : undefined;
}
function getConfiguredProtocol(message) {
    var _a;
    const protocol = (_a = getProtocolDefinition(message)) === null || _a === void 0 ? void 0 : _a.protocol;
    return typeof protocol === 'string' ? protocol : undefined;
}
function getComposedProtocolDependencies(message) {
    var _a;
    const uses = (_a = getProtocolDefinition(message)) === null || _a === void 0 ? void 0 : _a.uses;
    if (!uses) {
        return [];
    }
    return Object.values(uses).filter((protocol) => typeof protocol === 'string');
}
/**
 * Checks whether a message is an initial RecordsWrite (not an update).
 * An initial write has dateCreated === messageTimestamp (first write for this recordId).
 */
function isInitialWrite(message) {
    const desc = message.descriptor;
    if (!isRecordsWriteDescriptor(desc)) {
        return false;
    }
    // A RecordsWrite is initial if dateCreated === messageTimestamp (first write for this recordId).
    return desc.dateCreated === desc.messageTimestamp;
}
function getContextId(message) {
    return message.contextId;
}
export function getRoleContextPrefix(protocolPath, contextId) {
    const ancestorSegmentCount = protocolPath.split('/').length - 1;
    if (ancestorSegmentCount === 0 || contextId === undefined) {
        return undefined;
    }
    return contextId.split('/').slice(0, ancestorSegmentCount).join('/');
}
export function getRoleKey(protocol, protocolPath, recipient, contextPrefix) {
    return `${protocol}|${protocolPath}|${recipient}|${contextPrefix !== null && contextPrefix !== void 0 ? contextPrefix : ''}`;
}
function getRoleRecordKey(message) {
    const { descriptor } = message;
    if (!isRecordsWriteDescriptor(descriptor)) {
        return undefined;
    }
    const { protocol, protocolPath, recipient } = descriptor;
    if (protocol === undefined || protocolPath === undefined || recipient === undefined) {
        return undefined;
    }
    return getRoleKey(protocol, protocolPath, recipient, getRoleContextPrefix(protocolPath, getContextId(message)));
}
export function getSignaturePayload(message) {
    if (message.authorization === undefined) {
        return undefined;
    }
    try {
        return Jws.decodePlainObjectPayload(message.authorization.signature);
    }
    catch (_a) {
        return undefined;
    }
}
function getInvokedRoleKey(message, protocolDefinitionsByProtocol) {
    var _a, _b, _c;
    const { descriptor } = message;
    if (!isRecordsDescriptor(descriptor) || descriptor.protocol === undefined) {
        return undefined;
    }
    const protocolRole = (_a = getSignaturePayload(message)) === null || _a === void 0 ? void 0 : _a.protocolRole;
    const recipient = Message.getAuthor(message);
    if (typeof protocolRole !== 'string' || recipient === undefined) {
        return undefined;
    }
    let roleProtocol = descriptor.protocol;
    let roleProtocolPath = protocolRole;
    if (isCrossProtocolRef(protocolRole)) {
        const parsed = parseCrossProtocolRef(protocolRole);
        const referencedProtocol = parsed === undefined
            ? undefined
            : (_c = (_b = protocolDefinitionsByProtocol.get(descriptor.protocol)) === null || _b === void 0 ? void 0 : _b.uses) === null || _c === void 0 ? void 0 : _c[parsed.alias];
        if (parsed === undefined || referencedProtocol === undefined) {
            return undefined;
        }
        roleProtocol = referencedProtocol;
        roleProtocolPath = parsed.protocolPath;
    }
    return getRoleKey(roleProtocol, roleProtocolPath, recipient, getRoleContextPrefix(roleProtocolPath, getContextId(message)));
}
/**
 * Builds a dependency graph from the fetched messages and returns them in
 * dependency order so that dependencies are processed before dependents.
 *
 * Dependencies:
 * - ProtocolsConfigure must come before any RecordsWrite using that protocol
 * - Composed ProtocolsConfigure must come after ProtocolsConfigure messages for protocols in `uses`
 * - Parent record must come before child record (via parentId)
 * - Initial write must come before update writes (same recordId, not initial)
 * - Permission grants must come before messages that invoke them
 */
export function orderMessagesForAdmission(messages) {
    return buildMessageDependencyGraph(messages).sorted;
}
function buildMessageDependencyGraph(messages) {
    if (messages.length <= 1) {
        return {
            sorted: messages,
            dependencies: new Map(messages.map(message => [message, []])),
        };
    }
    const indexes = buildDependencyIndexes(messages);
    const dependencyEdges = buildDependencyEdges(messages, indexes);
    const sorted = sortDependencyGraph(messages, indexes.byIndex, dependencyEdges.edges, dependencyEdges.inDegree);
    return {
        sorted,
        dependencies: entriesByIndexDependencies(messages, dependencyEdges.dependenciesByIndex),
    };
}
function buildDependencyIndexes(messages) {
    const indexes = {
        byIndex: new Map(),
        protocolConfigureIndex: new Map(),
        protocolDefinitionsByProtocol: new Map(),
        initialWriteIndex: new Map(),
        grantIndex: new Map(),
        roleIndex: new Map(),
    };
    for (let i = 0; i < messages.length; i++) {
        const entry = messages[i];
        indexes.byIndex.set(i, entry);
        indexProtocolConfigure(entry.message, i, indexes);
        indexRecordsWrite(entry.message, i, indexes);
    }
    return indexes;
}
function indexProtocolConfigure(message, index, indexes) {
    const protocolUrl = getConfiguredProtocol(message);
    if (protocolUrl === undefined) {
        return;
    }
    const protocolDefinition = getProtocolDefinition(message);
    indexes.protocolConfigureIndex.set(protocolUrl, index);
    if (protocolDefinition !== undefined) {
        indexes.protocolDefinitionsByProtocol.set(protocolUrl, protocolDefinition);
    }
}
function indexRecordsWrite(message, index, indexes) {
    const desc = message.descriptor;
    if (!isRecordsWriteDescriptor(desc)) {
        return;
    }
    const recordId = getRecordId(message);
    if (recordId === undefined) {
        return;
    }
    if (isInitialWrite(message)) {
        indexes.initialWriteIndex.set(recordId, index);
    }
    if (desc.protocol === PermissionsProtocol.uri && desc.protocolPath === PermissionsProtocol.grantPath) {
        indexes.grantIndex.set(recordId, index);
    }
    const roleKey = getRoleRecordKey(message);
    if (roleKey !== undefined) {
        indexes.roleIndex.set(roleKey, index);
    }
}
function buildDependencyEdges(messages, indexes) {
    const dependencyEdges = {
        edges: new Map(),
        dependenciesByIndex: new Map(),
        inDegree: new Array(messages.length).fill(0),
    };
    const addEdge = (from, to) => {
        addDependencyEdge(dependencyEdges, from, to);
    };
    for (let i = 0; i < messages.length; i++) {
        addDependencyEdgesForMessage(i, messages[i].message, indexes, addEdge);
    }
    return dependencyEdges;
}
function addDependencyEdge(dependencyEdges, from, to) {
    if (from === to) {
        return;
    }
    let edgeSet = dependencyEdges.edges.get(from);
    if (edgeSet === undefined) {
        edgeSet = new Set();
        dependencyEdges.edges.set(from, edgeSet);
    }
    if (edgeSet.has(to)) {
        return;
    }
    edgeSet.add(to);
    addIndexDependency(dependencyEdges, from, to);
}
function addIndexDependency(dependencyEdges, from, to) {
    let dependencies = dependencyEdges.dependenciesByIndex.get(to);
    if (dependencies === undefined) {
        dependencies = new Set();
        dependencyEdges.dependenciesByIndex.set(to, dependencies);
    }
    dependencies.add(from);
    dependencyEdges.inDegree[to]++;
}
function addDependencyEdgesForMessage(index, message, indexes, addEdge) {
    const desc = message.descriptor;
    addComposedProtocolEdges(index, message, desc, indexes, addEdge);
    addRecordProtocolEdge(index, desc, indexes, addEdge);
    addParentRecordEdge(index, desc, indexes, addEdge);
    addInitialWriteEdge(index, message, desc, indexes, addEdge);
    addDeleteEdge(index, desc, indexes, addEdge);
    addPermissionGrantEdges(index, message, indexes, addEdge);
    addRoleEdge(index, message, indexes, addEdge);
}
function addComposedProtocolEdges(index, message, desc, indexes, addEdge) {
    if (!isProtocolsConfigureDescriptor(desc)) {
        return;
    }
    for (const protocol of getComposedProtocolDependencies(message)) {
        const protocolIndex = indexes.protocolConfigureIndex.get(protocol);
        if (protocolIndex !== undefined) {
            addEdge(protocolIndex, index);
        }
    }
}
function addRecordProtocolEdge(index, desc, indexes, addEdge) {
    if (!isRecordsDescriptor(desc) || desc.protocol === undefined) {
        return;
    }
    const protocolIndex = indexes.protocolConfigureIndex.get(desc.protocol);
    if (protocolIndex !== undefined) {
        addEdge(protocolIndex, index);
    }
}
function addParentRecordEdge(index, desc, indexes, addEdge) {
    if (!isRecordsDescriptor(desc) || desc.parentId === undefined) {
        return;
    }
    const parentIndex = indexes.initialWriteIndex.get(desc.parentId);
    if (parentIndex !== undefined) {
        addEdge(parentIndex, index);
    }
}
function addInitialWriteEdge(index, message, desc, indexes, addEdge) {
    if (!isRecordsWriteDescriptor(desc) || isInitialWrite(message)) {
        return;
    }
    const recordId = getRecordId(message);
    const initialIndex = recordId === undefined ? undefined : indexes.initialWriteIndex.get(recordId);
    if (initialIndex !== undefined) {
        addEdge(initialIndex, index);
    }
}
function addDeleteEdge(index, desc, indexes, addEdge) {
    if (!isRecordsDeleteDescriptor(desc) || desc.recordId === undefined) {
        return;
    }
    const initialIndex = indexes.initialWriteIndex.get(desc.recordId);
    if (initialIndex !== undefined) {
        addEdge(initialIndex, index);
    }
}
function addPermissionGrantEdges(index, message, indexes, addEdge) {
    for (const permissionGrantId of getInvokedPermissionGrantIds(message)) {
        const grantIndex = indexes.grantIndex.get(permissionGrantId);
        if (grantIndex !== undefined) {
            addEdge(grantIndex, index);
        }
    }
}
function addRoleEdge(index, message, indexes, addEdge) {
    const roleKey = getInvokedRoleKey(message, indexes.protocolDefinitionsByProtocol);
    const roleIndex = roleKey === undefined ? undefined : indexes.roleIndex.get(roleKey);
    if (roleIndex !== undefined) {
        addEdge(roleIndex, index);
    }
}
function sortDependencyGraph(messages, byIndex, edges, inDegree) {
    const queue = zeroInDegreeQueue(inDegree);
    const sorted = [];
    while (queue.length > 0) {
        const node = queue.shift();
        sorted.push(byIndex.get(node));
        decrementNeighborDegrees(node, edges, inDegree, queue);
    }
    appendCyclicEntries(messages, byIndex, sorted);
    return sorted;
}
function zeroInDegreeQueue(inDegree) {
    const queue = [];
    for (let i = 0; i < inDegree.length; i++) {
        if (inDegree[i] === 0) {
            queue.push(i);
        }
    }
    return queue;
}
function decrementNeighborDegrees(node, edges, inDegree, queue) {
    const neighbors = edges.get(node);
    if (neighbors === undefined) {
        return;
    }
    for (const neighbor of neighbors) {
        inDegree[neighbor]--;
        if (inDegree[neighbor] === 0) {
            queue.push(neighbor);
        }
    }
}
function appendCyclicEntries(messages, byIndex, sorted) {
    if (sorted.length >= messages.length) {
        return;
    }
    const sortedSet = new Set(sorted);
    for (let i = 0; i < messages.length; i++) {
        const entry = byIndex.get(i);
        if (!sortedSet.has(entry)) {
            sorted.push(entry);
        }
    }
}
function entriesByIndexDependencies(messages, dependenciesByIndex) {
    var _a;
    const dependencies = new Map();
    for (let i = 0; i < messages.length; i++) {
        const indexDependencies = (_a = dependenciesByIndex.get(i)) !== null && _a !== void 0 ? _a : new Set();
        dependencies.set(messages[i], [...indexDependencies].map(index => messages[index]));
    }
    return dependencies;
}
//# sourceMappingURL=sync-admission-order.js.map