import { Cid } from '../utils/cid.js';
import { Encryption } from '../utils/encryption.js';
import { Jws } from '../utils/jws.js';
import { Message } from '../core/message.js';
import { PermissionGrant } from '../protocols/permission-grant.js';
import { Records } from '../utils/records.js';
import { RecordsGrantAuthorization } from '../core/records-grant-authorization.js';
import { removeUndefinedProperties } from '@enbox/common';
import { Time } from '../utils/time.js';
import { createAttestation, createEncryptionProperty, createSignerSignature, validateAttestationIntegrity, } from './records-write-signing.js';
import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
import { DwnInterfaceName, DwnMethodName } from '../enums/dwn-interface-method.js';
import { fetchInitialRecordsWrite, fetchInitialRecordsWriteMessage, fetchNewestRecordsWrite, getAttesters, getInitialWrite, verifyEqualityOfImmutableProperties, } from './records-write-query.js';
import { normalizeProtocolUrl, normalizeSchemaUrl, validateProtocolUrlNormalized, validateSchemaUrlNormalized } from '../utils/url.js';
/**
 * A class representing a RecordsWrite DWN message.
 * NOTE: Unable to extend `AbstractMessage` directly because the incompatible `_message` type, which is not just a generic `<M>` type.
 */
export class RecordsWrite {
    parentContextId;
    _message;
    /**
     * Valid JSON message representing this RecordsWrite.
     * @throws `DwnErrorCode.RecordsWriteMissingSigner` if the message is not signed yet.
     */
    get message() {
        if (this._message.authorization === undefined) {
            throw new DwnError(DwnErrorCode.RecordsWriteMissingSigner, 'This RecordsWrite is not yet signed, JSON message cannot be generated from an incomplete state.');
        }
        return this._message;
    }
    _author;
    get author() {
        return this._author;
    }
    _signaturePayload;
    get signaturePayload() {
        return this._signaturePayload;
    }
    _owner;
    /**
     * The owner DID of the message if owner signature is present in the message; `undefined` otherwise.
     * This is the logical owner of the message, not to be confused with the actual signer of the owner signature,
     * this is because the signer of the owner signature may not be the actual DWN owner, but a delegate authorized by the owner.
     */
    get owner() {
        return this._owner;
    }
    _ownerSignaturePayload;
    /**
     * Decoded owner signature payload.
     */
    get ownerSignaturePayload() {
        return this._ownerSignaturePayload;
    }
    /**
     * If this message is signed by an author-delegate.
     */
    get isSignedByAuthorDelegate() {
        return Message.isSignedByAuthorDelegate(this._message);
    }
    /**
     * If this message is signed by an owner-delegate.
     */
    get isSignedByOwnerDelegate() {
        return Message.isSignedByOwnerDelegate(this._message);
    }
    /**
     * Gets the signer of this message.
     * This is not to be confused with the logical author of the message.
     */
    get signer() {
        return Message.getSigner(this._message);
    }
    /**
     * Gets the signer of owner signature; `undefined` if owner signature is not present in the message.
     * This is not to be confused with the logical owner {@link #owner} of the message,
     * this is because the signer of the owner signature may not be the actual DWN owner, but a delegate authorized by the owner.
     * In the case that the owner signature is signed by the actual DWN owner, this value will be the same as {@link #owner}.
     */
    get ownerSignatureSigner() {
        if (this._message.authorization?.ownerSignature === undefined) {
            return undefined;
        }
        const signer = Jws.getSignerDid(this._message.authorization.ownerSignature.signatures[0]);
        return signer;
    }
    attesters;
    constructor(message, parentContextId) {
        this.parentContextId = parentContextId;
        this._message = message;
        if (message.authorization !== undefined) {
            this._author = Message.getAuthor(message);
            this._signaturePayload = Jws.decodePlainObjectPayload(message.authorization.signature);
            if (message.authorization.ownerSignature !== undefined) {
                // if the message authorization contains owner delegated grant, the owner would be the grantor of the grant
                // else the owner would be the signer of the owner signature
                if (message.authorization.ownerDelegatedGrant === undefined) {
                    this._owner = Jws.getSignerDid(message.authorization.ownerSignature.signatures[0]);
                }
                else {
                    this._owner = Message.getSigner(message.authorization.ownerDelegatedGrant);
                }
                this._ownerSignaturePayload = Jws.decodePlainObjectPayload(message.authorization.ownerSignature);
            }
        }
        this.attesters = getAttesters(message);
        // consider converting isInitialWrite() & getEntryId() into properties for performance and convenience
    }
    /**
     * Parses a RecordsWrite message and returns a {RecordsWrite} instance.
     */
    static async parse(recordsWriteMessage) {
        // Make a copy so that the stored copy is not subject to external, unexpected modification.
        const message = JSON.parse(JSON.stringify(recordsWriteMessage));
        // Validate the message against the JSON schema.
        // We strip internal properties that the MessageStore may attach to stored messages
        // but are not part of the RecordsWrite JSON schema:
        //   - `encodedData`:  base64url-encoded payload for small records
        //   - `initialWrite`: the initial RecordsWrite when this message is an update
        const { encodedData: _, initialWrite: __, ...messageToValidate } = message;
        Message.validateJsonSchema(messageToValidate);
        if (message.encryption !== undefined) {
            Encryption.validateEncryptionProperty(message.encryption);
        }
        // asynchronous checks that are required by the constructor to initialize members properly
        await Message.validateSignatureStructure(message.authorization.signature, message.descriptor, 'RecordsWriteSignaturePayload');
        if (message.authorization.ownerSignature !== undefined) {
            await Message.validateSignatureStructure(message.authorization.ownerSignature, message.descriptor, 'GenericSignaturePayload', { validatePermissionGrantInvocation: false });
        }
        await validateAttestationIntegrity(message);
        const recordsWrite = new RecordsWrite(message);
        await recordsWrite.validateIntegrity(); // RecordsWrite specific data integrity check
        return recordsWrite;
    }
    /**
     * Creates a RecordsWrite message.
     * @param options.recordId If `undefined`, will be auto-filled as the initial message as convenience for developer.
     * @param options.data Data used to compute the `dataCid`, must be the encrypted data bytes if `options.encryptionInput` is given.
     *                     Must specify `options.dataCid` if `undefined`.
     * @param options.dataCid CID of the data that is already stored in the DWN. Must specify `options.data` if `undefined`.
     * @param options.dataSize Size of data in number of bytes. Must be defined if `options.dataCid` is defined; must be `undefined` otherwise.
     * @param options.dateCreated If `undefined`, it will be auto-filled with current time.
     * @param options.messageTimestamp If `undefined`, it will be auto-filled with current time.
     * @param options.parentContextId Must be given if this message is for a non-root protocol record.
     *                                If not given, it means this write is for a root protocol record.
     */
    static async create(options) {
        RecordsWrite.validateCreateOptions(options);
        const dataCid = options.dataCid ?? await Cid.computeDagPbCidFromBytes(options.data);
        const dataSize = options.dataSize ?? options.data.length;
        const currentTime = Time.getCurrentTimestamp();
        const permissionGrantInvocation = Message.normalizePermissionGrantInvocation({
            permissionGrantId: options.permissionGrantId,
        });
        const descriptor = {
            interface: DwnInterfaceName.Records,
            method: DwnMethodName.Write,
            protocol: normalizeProtocolUrl(options.protocol),
            protocolPath: options.protocolPath,
            recipient: options.recipient,
            schema: options.schema === undefined ? undefined : normalizeSchemaUrl(options.schema),
            tags: options.tags,
            parentId: RecordsWrite.getRecordIdFromContextId(options.parentContextId),
            dataCid,
            dataSize,
            dateCreated: options.dateCreated ?? currentTime,
            messageTimestamp: options.messageTimestamp ?? currentTime,
            published: options.published,
            datePublished: options.datePublished,
            dataFormat: options.dataFormat,
            squash: options.squash,
            ...permissionGrantInvocation,
        };
        // generate `datePublished` if the message is to be published but `datePublished` is not given
        if (options.published === true &&
            options.datePublished === undefined) {
            descriptor.datePublished = currentTime;
        }
        // delete all descriptor properties that are `undefined` else the code will encounter the following IPLD issue when attempting to generate CID:
        // Error: `undefined` is not supported by the IPLD Data Model and cannot be encoded
        removeUndefinedProperties(descriptor);
        // `recordId` computation
        const recordId = options.recordId;
        // `attestation` generation
        const descriptorCid = await Cid.computeCid(descriptor);
        const attestation = await createAttestation(descriptorCid, options.attestationSigners);
        // `encryption` generation
        const encryption = options.encryption ?? await createEncryptionProperty(options.encryptionInput);
        const message = {
            recordId,
            descriptor
        };
        // assign optional properties only if they exist
        if (attestation !== undefined) {
            message.attestation = attestation;
        }
        if (encryption !== undefined) {
            message.encryption = encryption;
        }
        const recordsWrite = new RecordsWrite(message, options.parentContextId);
        if (options.signer !== undefined) {
            await recordsWrite.sign({
                signer: options.signer,
                delegatedGrant: options.delegatedGrant,
                ...permissionGrantInvocation,
                protocolRole: options.protocolRole,
            });
        }
        return recordsWrite;
    }
    static validateCreateOptions(options) {
        if (options.protocol === undefined || options.protocolPath === undefined) {
            throw new DwnError(DwnErrorCode.RecordsWriteCreateMissingProtocol, '`protocol` and `protocolPath` are required');
        }
        if ((options.data === undefined && options.dataCid === undefined) ||
            (options.data !== undefined && options.dataCid !== undefined)) {
            throw new DwnError(DwnErrorCode.RecordsWriteCreateDataAndDataCidMutuallyExclusive, 'one and only one parameter between `data` and `dataCid` is required');
        }
        if ((options.dataCid === undefined && options.dataSize !== undefined) ||
            (options.dataCid !== undefined && options.dataSize === undefined)) {
            throw new DwnError(DwnErrorCode.RecordsWriteCreateDataCidAndDataSizeMutuallyInclusive, '`dataCid` and `dataSize` must both be defined or undefined at the same time');
        }
        if (options.signer === undefined && options.delegatedGrant !== undefined) {
            throw new DwnError(DwnErrorCode.RecordsWriteCreateMissingSigner, '`signer` must be given when `delegatedGrant` is given');
        }
        if (options.encryption !== undefined && options.encryptionInput !== undefined) {
            throw new DwnError(DwnErrorCode.RecordsWriteCreateEncryptionAndEncryptionInputMutuallyExclusive, '`encryption` and `encryptionInput` cannot both be defined');
        }
    }
    static getRecordIdFromContextId(contextId) {
        return contextId?.split('/').filter(segment => segment !== '').pop();
    }
    /**
     * Convenience method that creates a message by:
     * 1. Copying over immutable properties from the given source message
     * 2. Copying over mutable properties that are not overwritten from the given source message
     * 3. Replace the mutable properties that are given new value
     * @param options.recordsWriteMessage Message that the new RecordsWrite will be based from.
     * @param options.messageTimestamp The new date the record is modified. If not given, current time will be used .
     * @param options.data The new data or the record. If not given, data from given message will be used.
     * @param options.published The new published state. If not given, then will be set to `true` if {options.messageTimestamp} is given;
     * else the state from given message will be used.
     * @param options.publishedDate The new date the record is modified. If not given, then:
     * - will not be set if the record will be unpublished as the result of this RecordsWrite; else
     * - will be set to the same published date as the given message if it wss already published; else
     * - will be set to current time (because this is a toggle from unpublished to published)
     */
    static async createFrom(options) {
        const sourceMessage = options.recordsWriteMessage;
        const sourceRecordsWrite = await RecordsWrite.parse(sourceMessage);
        const currentTime = Time.getCurrentTimestamp();
        // inherit published value from parent if neither published nor datePublished is specified
        const published = options.published ?? (options.datePublished ? true : sourceMessage.descriptor.published);
        // use current time if published but no explicit time given
        let datePublished = undefined;
        // if given explicitly published dated
        if (options.datePublished) {
            datePublished = options.datePublished;
        }
        else {
            // if this RecordsWrite will publish the record
            if (published) {
                // the parent was already published, inherit the same published date
                if (sourceMessage.descriptor.published) {
                    datePublished = sourceMessage.descriptor.datePublished;
                }
                else {
                    // this is a toggle from unpublished to published, use current time
                    datePublished = currentTime;
                }
            }
        }
        const createOptions = {
            // immutable properties below, just copy from the source message
            recipient: sourceMessage.descriptor.recipient,
            recordId: sourceMessage.recordId,
            dateCreated: sourceMessage.descriptor.dateCreated,
            protocol: sourceMessage.descriptor.protocol,
            protocolPath: sourceMessage.descriptor.protocolPath,
            schema: sourceMessage.descriptor.schema,
            parentContextId: Records.getParentContextFromOfContextId(sourceMessage.contextId),
            // mutable properties below
            messageTimestamp: options.messageTimestamp ?? currentTime,
            published,
            datePublished,
            tags: options.tags,
            data: options.data,
            dataCid: options.data ? undefined : sourceMessage.descriptor.dataCid, // if new `data` not given, use value from source message
            dataSize: options.data ? undefined : sourceMessage.descriptor.dataSize, // if new `data` not given, use value from source message
            dataFormat: options.dataFormat ?? sourceMessage.descriptor.dataFormat,
            protocolRole: options.protocolRole ?? sourceRecordsWrite.signaturePayload.protocolRole, // if not given, use value from source message
            delegatedGrant: options.delegatedGrant,
            // finally still need signers
            signer: options.signer,
            attestationSigners: options.attestationSigners,
            encryption: options.encryptionInput === undefined && options.data === undefined ? sourceMessage.encryption : undefined,
            encryptionInput: options.encryptionInput,
        };
        const recordsWrite = await RecordsWrite.create(createOptions);
        return recordsWrite;
    }
    /**
     * Validates the expected `dataCid` and `dataSize` in the descriptor against the actual data.
     *
     * @throws {DwnError} with `DwnErrorCode.RecordsWriteDataCidMismatch`
     *                    if the actual data CID does not match `dataCid` in the descriptor.
     * @throws {DwnError} with `DwnErrorCode.RecordsWriteDataSizeMismatch`
     *                    if the actual byte size does not match `dataSize` in the descriptor.
     */
    static validateDataIntegrity(expectedDataCid, expectedDataSize, actualDataCid, actualDataSize) {
        if (expectedDataCid !== actualDataCid) {
            throw new DwnError(DwnErrorCode.RecordsWriteDataCidMismatch, `actual data CID ${actualDataCid} does not match dataCid in descriptor: ${expectedDataCid}`);
        }
        if (expectedDataSize !== actualDataSize) {
            throw new DwnError(DwnErrorCode.RecordsWriteDataSizeMismatch, `actual data size ${actualDataSize} bytes does not match dataSize in descriptor: ${expectedDataSize}`);
        }
    }
    /**
     * Called by `JSON.stringify(...)` automatically.
     */
    toJSON() {
        return this.message;
    }
    /**
     * Encrypts the symmetric encryption key using the public keys given and attaches the resulting `encryption` property.
     */
    async encryptSymmetricEncryptionKey(encryptionInput) {
        this._message.encryption = await createEncryptionProperty(encryptionInput);
        delete this._message.authorization;
        this._signaturePayload = undefined;
        this._author = undefined;
    }
    /**
     * Signs the RecordsWrite, the signer is commonly the author, but can also be a delegate.
     */
    async sign(options) {
        const { signer, delegatedGrant, permissionGrantId, protocolRole } = options;
        // compute delegated grant ID and author if delegated grant is given
        let delegatedGrantId;
        let authorDid;
        if (delegatedGrant === undefined) {
            authorDid = Jws.extractDid(signer.keyId);
        }
        else {
            delegatedGrantId = await Message.getCid(delegatedGrant);
            authorDid = Jws.getSignerDid(delegatedGrant.authorization.signature.signatures[0]);
        }
        const descriptor = this._message.descriptor;
        const descriptorCid = await Cid.computeCid(descriptor);
        // compute `recordId` if not given at construction time
        this._message.recordId = this._message.recordId ?? await RecordsWrite.getEntryId(authorDid, descriptor);
        // compute `contextId` — all records belong to a protocol
        if (this.parentContextId === undefined || this.parentContextId === '') {
            // root protocol record
            this._message.contextId = this._message.recordId;
        }
        else {
            // non-root protocol record
            this._message.contextId = this.parentContextId + '/' + this._message.recordId;
        }
        // `signature` generation
        const signature = await createSignerSignature({
            recordId: this._message.recordId,
            contextId: this._message.contextId, // contextId is computed just above, always defined here
            descriptorCid,
            attestation: this._message.attestation,
            encryption: this._message.encryption,
            signer,
            delegatedGrantId,
            permissionGrantId,
            protocolRole
        });
        this._message.authorization = { signature };
        if (delegatedGrant !== undefined) {
            this._message.authorization.authorDelegatedGrant = delegatedGrant;
        }
        // there is opportunity to optimize here as the payload is constructed within `createAuthorization(...)`
        this._signaturePayload = Jws.decodePlainObjectPayload(signature);
        this._author = authorDid;
    }
    /**
     * Signs the `RecordsWrite` as the DWN owner.
     * This is used when the DWN owner wants to retain a copy of a message that the owner did not author.
     * NOTE: requires the `RecordsWrite` to already have the author's signature.
     */
    async signAsOwner(signer) {
        if (this._author === undefined) {
            throw new DwnError(DwnErrorCode.RecordsWriteSignAsOwnerUnknownAuthor, 'Unable to sign as owner without message signature because owner needs to sign over `recordId` which depends on author DID.');
        }
        const descriptor = this._message.descriptor;
        const ownerSignature = await Message.createSignature(descriptor, signer);
        this._message.authorization.ownerSignature = ownerSignature;
        this._ownerSignaturePayload = Jws.decodePlainObjectPayload(ownerSignature);
        this._owner = Jws.extractDid(signer.keyId);
    }
    /**
     * Signs the `RecordsWrite` as the DWN owner-delegate.
     * This is used when a DWN owner-delegate wants to retain a copy of a message that the owner did not author.
     * NOTE: requires the `RecordsWrite` to already have the author's signature.
     */
    async signAsOwnerDelegate(signer, delegatedGrant) {
        if (this._author === undefined) {
            throw new DwnError(DwnErrorCode.RecordsWriteSignAsOwnerDelegateUnknownAuthor, 'Unable to sign as owner delegate without message signature because owner delegate needs to sign over `recordId` which depends on author DID.');
        }
        const delegatedGrantId = await Message.getCid(delegatedGrant);
        const descriptor = this._message.descriptor;
        const ownerSignature = await Message.createSignature(descriptor, signer, { delegatedGrantId });
        this._message.authorization.ownerSignature = ownerSignature;
        this._message.authorization.ownerDelegatedGrant = delegatedGrant;
        this._ownerSignaturePayload = Jws.decodePlainObjectPayload(ownerSignature);
        this._owner = Jws.getSignerDid(delegatedGrant.authorization.signature.signatures[0]);
    }
    /**
     * Validates the integrity of the RecordsWrite message assuming the message passed basic schema validation.
     * There is opportunity to integrate better with `validateSchema(...)`
     */
    async validateIntegrity() {
        // if the new message is the initial write
        const isInitialWrite = await this.isInitialWrite();
        if (isInitialWrite) {
            // `messageTimestamp` and `dateCreated` equality check
            const dateRecordCreated = this.message.descriptor.dateCreated;
            const messageTimestamp = this.message.descriptor.messageTimestamp;
            if (messageTimestamp !== dateRecordCreated) {
                throw new DwnError(DwnErrorCode.RecordsWriteValidateIntegrityDateCreatedMismatch, `messageTimestamp ${messageTimestamp} must match dateCreated ${dateRecordCreated} for the initial write`);
            }
            // if the message is a protocol context root, the `contextId` must match the expected deterministic value
            if (this.message.descriptor.parentId === undefined) {
                const expectedContextId = await this.getEntryId();
                if (this.message.contextId !== expectedContextId) {
                    throw new DwnError(DwnErrorCode.RecordsWriteValidateIntegrityContextIdMismatch, `contextId in message: ${this.message.contextId} does not match deterministic contextId: ${expectedContextId}`);
                }
            }
        }
        // NOTE: validateSignatureStructure() call earlier enforces the presence of `authorization` and thus `signature` in RecordsWrite
        const signaturePayload = this.signaturePayload;
        // make sure the `recordId` in message is the same as the `recordId` in the payload of the message signature
        if (this.message.recordId !== signaturePayload.recordId) {
            throw new DwnError(DwnErrorCode.RecordsWriteValidateIntegrityRecordIdUnauthorized, `recordId in message ${this.message.recordId} does not match recordId in authorization: ${signaturePayload.recordId}`);
        }
        // if `contextId` is given in message, make sure the same `contextId` is in the payload of the message signature
        if (this.message.contextId !== signaturePayload.contextId) {
            throw new DwnError(DwnErrorCode.RecordsWriteValidateIntegrityContextIdNotInSignerSignaturePayload, `contextId in message ${this.message.contextId} does not match contextId in authorization: ${signaturePayload.contextId}`);
        }
        await Records.validateDelegatedGrantReferentialIntegrity(this.message, signaturePayload, this.ownerSignaturePayload);
        // if `attestation` is given in message, make sure the correct `attestationCid` is in the payload of the message signature
        if (signaturePayload.attestationCid !== undefined) {
            const expectedAttestationCid = await Cid.computeCid(this.message.attestation);
            const actualAttestationCid = signaturePayload.attestationCid;
            if (actualAttestationCid !== expectedAttestationCid) {
                throw new DwnError(DwnErrorCode.RecordsWriteValidateIntegrityAttestationMismatch, `CID ${expectedAttestationCid} of attestation property in message does not match attestationCid in authorization: ${actualAttestationCid}`);
            }
        }
        // if `encryption` is given in message, make sure the correct `encryptionCid` is in the payload of the message signature
        if (this.message.encryption !== undefined && signaturePayload.encryptionCid === undefined) {
            throw new DwnError(DwnErrorCode.RecordsWriteValidateIntegrityEncryptionCidMissing, 'encryptionCid must be present in the authorization payload when encryption is present in the message');
        }
        if (this.message.encryption === undefined && signaturePayload.encryptionCid !== undefined) {
            throw new DwnError(DwnErrorCode.RecordsWriteValidateIntegrityEncryptionCidMissing, 'encryption must be present in the message when encryptionCid is present in the authorization payload');
        }
        if (this.message.encryption !== undefined) {
            const expectedEncryptionCid = await Cid.computeCid(this.message.encryption);
            const actualEncryptionCid = signaturePayload.encryptionCid;
            if (actualEncryptionCid !== expectedEncryptionCid) {
                throw new DwnError(DwnErrorCode.RecordsWriteValidateIntegrityEncryptionCidMismatch, `CID ${expectedEncryptionCid} of encryption property in message does not match encryptionCid in authorization: ${actualEncryptionCid}`);
            }
        }
        validateProtocolUrlNormalized(this.message.descriptor.protocol);
        if (this.message.descriptor.schema !== undefined) {
            validateSchemaUrlNormalized(this.message.descriptor.schema);
        }
        Time.validateTimestamp(this.message.descriptor.messageTimestamp);
        Time.validateTimestamp(this.message.descriptor.dateCreated);
        if (this.message.descriptor.datePublished) {
            Time.validateTimestamp(this.message.descriptor.datePublished);
        }
    }
    /**
     * Computes the deterministic Entry ID of this message.
     */
    async getEntryId() {
        const entryId = await RecordsWrite.getEntryId(this.author, this.message.descriptor);
        return entryId;
    }
    ;
    /**
     * Computes the deterministic Entry ID of this message.
     */
    static async getEntryId(author, descriptor) {
        if (author === undefined) {
            throw new DwnError(DwnErrorCode.RecordsWriteGetEntryIdUndefinedAuthor, 'Property `author` is needed to compute entry ID.');
        }
        const entryIdInput = { ...descriptor, author };
        const cid = await Cid.computeCid(entryIdInput);
        return cid;
    }
    ;
    /**
     * Checks if the given message is the initial entry of a record.
     */
    async isInitialWrite() {
        const entryId = await this.getEntryId();
        return (entryId === this.message.recordId);
    }
    async constructIndexes(isLatestBaseState) {
        const message = this.message;
        // we want to process tags separately from the rest of descriptors as it is an object and not a primitive KeyValue type.
        const { tags, ...descriptor } = message.descriptor;
        delete descriptor.published; // handle `published` specifically further down
        delete descriptor.squash; // handle `squash` specifically further down
        let indexes = {
            ...descriptor,
            isLatestBaseState,
            published: !!message.descriptor.published,
            squash: !!message.descriptor.squash,
            author: this.author, //author will not be undefined when indexes are constructed as it's been authorized
            recordId: message.recordId,
            entryId: await RecordsWrite.getEntryId(this.author, this.message.descriptor)
        };
        // in order to avoid name clashes with first-class index keys
        // we build the indexes with `tag.property_name` for each tag property.
        // we only index tags if the message is the latest base state, as that's the only time filtering for tags is relevant.
        if (tags !== undefined && isLatestBaseState === true) {
            const flattenedTags = Records.buildTagIndexes({ ...tags });
            indexes = { ...indexes, ...flattenedTags };
        }
        // add additional indexes to optional values if given
        // TODO: index multiple attesters (https://github.com/enboxorg/enbox/issues/223)
        if (this.attesters.length > 0) {
            indexes.attester = this.attesters[0];
        }
        indexes.contextId = message.contextId;
        return indexes;
    }
    /**
     * Authorizes the author-delegate who signed this message.
     * @param validationStateReader Used to check if the grant has been revoked.
     */
    async authorizeAuthorDelegate(validationStateReader) {
        const delegatedGrant = PermissionGrant.parse(this.message.authorization.authorDelegatedGrant);
        await RecordsGrantAuthorization.authorizeWrite({
            recordsWriteMessage: this.message,
            expectedGrantor: this.author,
            expectedGrantee: this.signer,
            permissionGrant: delegatedGrant,
            validationStateReader
        });
    }
    /**
     * Authorizes the owner-delegate who signed this message.
     * @param validationStateReader Used to check if the grant has been revoked.
     */
    async authorizeOwnerDelegate(validationStateReader) {
        const delegatedGrant = PermissionGrant.parse(this.message.authorization.ownerDelegatedGrant);
        await RecordsGrantAuthorization.authorizeWrite({
            recordsWriteMessage: this.message,
            expectedGrantor: this.owner,
            expectedGrantee: this.ownerSignatureSigner,
            permissionGrant: delegatedGrant,
            validationStateReader
        });
    }
    /**
     * Checks if the given message is the initial entry of a record.
     */
    static async isInitialWrite(message) {
        // can't be the initial write if the message is not a Records Write
        if (message.descriptor.interface !== DwnInterfaceName.Records ||
            message.descriptor.method !== DwnMethodName.Write) {
            return false;
        }
        const recordsWriteMessage = message;
        const author = Message.getAuthor(recordsWriteMessage);
        const entryId = await RecordsWrite.getEntryId(author, recordsWriteMessage.descriptor);
        return (entryId === recordsWriteMessage.recordId);
    }
    /** Delegate to `createAttestation` in `records-write-signing.ts`. */
    static async createAttestation(descriptorCid, signers) {
        return createAttestation(descriptorCid, signers);
    }
    /** Delegate to `createSignerSignature` in `records-write-signing.ts`. */
    static async createSignerSignature(input) {
        return createSignerSignature(input);
    }
    /** Delegate to `getInitialWrite` in `records-write-query.ts`. */
    static async getInitialWrite(messages) {
        return getInitialWrite(messages);
    }
    /** Delegate to `verifyEqualityOfImmutableProperties` in `records-write-query.ts`. */
    static verifyEqualityOfImmutableProperties(existingWriteMessage, newMessage) {
        return verifyEqualityOfImmutableProperties(existingWriteMessage, newMessage);
    }
    /** Delegate to `getAttesters` in `records-write-query.ts`. */
    static getAttesters(message) {
        return getAttesters(message);
    }
    /** Delegate to `fetchNewestRecordsWrite` in `records-write-query.ts`. */
    static async fetchNewestRecordsWrite(messageStore, tenant, recordId) {
        return fetchNewestRecordsWrite(messageStore, tenant, recordId);
    }
    /** Delegate to `fetchInitialRecordsWrite` in `records-write-query.ts`. */
    static async fetchInitialRecordsWrite(messageStore, tenant, recordId) {
        return fetchInitialRecordsWrite(messageStore, tenant, recordId);
    }
    /** Delegate to `fetchInitialRecordsWriteMessage` in `records-write-query.ts`. */
    static async fetchInitialRecordsWriteMessage(messageStore, tenant, recordId) {
        return fetchInitialRecordsWriteMessage(messageStore, tenant, recordId);
    }
}
//# sourceMappingURL=records-write.js.map