"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MessageFactory = exports.createSignedMessage = void 0;
const protocol_1 = require("@streamr/protocol");
const utils_1 = require("@streamr/utils");
const random_1 = __importDefault(require("lodash/random"));
const StreamrClientError_1 = require("../StreamrClientError");
const EncryptionUtil_1 = require("../encryption/EncryptionUtil");
const signature_1 = require("../signature");
const Mapping_1 = require("../utils/Mapping");
const utils_2 = require("../utils/utils");
const messageChain_1 = require("./messageChain");
const validateStreamMessage_1 = require("../utils/validateStreamMessage");
const createSignedMessage = async (opts) => {
    const signature = await opts.authentication.createMessageSignature((0, signature_1.createSignaturePayload)({
        messageId: opts.messageId,
        messageType: opts.messageType,
        content: opts.content,
        signatureType: opts.signatureType,
        encryptionType: opts.encryptionType,
        prevMsgRef: opts.prevMsgRef ?? undefined,
        newGroupKey: opts.newGroupKey ?? undefined
    }));
    return new protocol_1.StreamMessage({
        ...opts,
        signature,
        content: opts.content
    });
};
exports.createSignedMessage = createSignedMessage;
class MessageFactory {
    streamId;
    authentication;
    defaultPartition;
    defaultMessageChainIds;
    prevMsgRefs = new Map();
    streamRegistry;
    groupKeyQueue;
    erc1271ContractFacade;
    firstMessage = true;
    constructor(opts) {
        this.streamId = opts.streamId;
        this.authentication = opts.authentication;
        this.streamRegistry = opts.streamRegistry;
        this.groupKeyQueue = opts.groupKeyQueue;
        this.erc1271ContractFacade = opts.erc1271ContractFacade;
        this.defaultMessageChainIds = new Mapping_1.Mapping(async () => {
            return (0, messageChain_1.createRandomMsgChainId)();
        });
    }
    /* eslint-disable padding-line-between-statements */
    async createMessage(content, metadata, explicitPartition) {
        const publisherId = await this.getPublisherId(metadata);
        const isPublisher = await this.streamRegistry.isStreamPublisher(this.streamId, publisherId);
        if (!isPublisher) {
            this.streamRegistry.clearStreamCache(this.streamId);
            throw new StreamrClientError_1.StreamrClientError(`You don't have permission to publish to this stream. Using address: ${publisherId}`, 'MISSING_PERMISSION');
        }
        const partitionCount = (await this.streamRegistry.getStream(this.streamId)).getMetadata().partitions;
        let partition;
        if (explicitPartition !== undefined) {
            if ((explicitPartition < 0 || explicitPartition >= partitionCount)) {
                throw new Error(`Partition ${explicitPartition} is out of range (0..${partitionCount - 1})`);
            }
            if (metadata.partitionKey !== undefined) {
                throw new Error('Invalid combination of "partition" and "partitionKey"');
            }
            partition = explicitPartition;
        }
        else {
            partition = (metadata.partitionKey !== undefined)
                ? (0, utils_1.keyToArrayIndex)(partitionCount, metadata.partitionKey)
                : this.getDefaultPartition(partitionCount);
        }
        const msgChainId = metadata.msgChainId ?? await this.defaultMessageChainIds.get(partition);
        const msgChainKey = (0, utils_2.formLookupKey)(partition, msgChainId);
        const prevMsgRef = this.prevMsgRefs.get(msgChainKey);
        const msgRef = (0, messageChain_1.createMessageRef)(metadata.timestamp, prevMsgRef);
        this.prevMsgRefs.set(msgChainKey, msgRef);
        const messageId = new protocol_1.MessageID(this.streamId, partition, msgRef.timestamp, msgRef.sequenceNumber, publisherId, msgChainId);
        const encryptionType = (await this.streamRegistry.hasPublicSubscribePermission(this.streamId)) ? protocol_1.EncryptionType.NONE : protocol_1.EncryptionType.AES;
        let groupKeyId;
        let newGroupKey;
        let rawContent;
        let contentType;
        if (content instanceof Uint8Array) {
            contentType = protocol_1.ContentType.BINARY;
            rawContent = content;
        }
        else {
            contentType = protocol_1.ContentType.JSON;
            rawContent = (0, utils_1.utf8ToBinary)(JSON.stringify(content));
        }
        if (encryptionType === protocol_1.EncryptionType.AES) {
            const keySequence = await this.groupKeyQueue.useGroupKey();
            rawContent = EncryptionUtil_1.EncryptionUtil.encryptWithAES(rawContent, keySequence.current.data);
            groupKeyId = keySequence.current.id;
            if (keySequence.next !== undefined) {
                newGroupKey = keySequence.current.encryptNextGroupKey(keySequence.next);
            }
        }
        const msg = await (0, exports.createSignedMessage)({
            messageId,
            messageType: protocol_1.StreamMessageType.MESSAGE,
            content: rawContent,
            prevMsgRef,
            encryptionType,
            groupKeyId,
            newGroupKey,
            authentication: this.authentication,
            contentType,
            signatureType: metadata.erc1271Contract !== undefined ? protocol_1.SignatureType.ERC_1271 : protocol_1.SignatureType.SECP256K1,
        });
        // Assert the signature is valid for the first message. This is done here to improve user experience
        // in case the client signer is not authorized for the ERC-1271 contract.
        if (this.firstMessage) {
            this.firstMessage = false;
            if (metadata.erc1271Contract !== undefined) {
                await (0, validateStreamMessage_1.assertSignatureIsValid)(msg, this.erc1271ContractFacade);
            }
        }
        return msg;
    }
    async getPublisherId(metadata) {
        if (metadata.erc1271Contract !== undefined) {
            return (0, utils_1.toEthereumAddress)(metadata.erc1271Contract);
        }
        else {
            return this.authentication.getAddress();
        }
    }
    getDefaultPartition(partitionCount) {
        // we want to (re-)select a random partition in these two situations
        // 1) this is the first publish, and we have not yet selected any partition (the most typical case)
        // 2) the partition count may have decreased since we initially selected a random partitions, and it
        //    is now out-of-range (very rare case)
        if ((this.defaultPartition === undefined) || (this.defaultPartition >= partitionCount)) {
            this.defaultPartition = (0, random_1.default)(partitionCount - 1);
        }
        return this.defaultPartition;
    }
}
exports.MessageFactory = MessageFactory;
//# sourceMappingURL=MessageFactory.js.map