/**
 * @packageDocumentation
 *
 * The peer store is where libp2p stores data about the peers it has encountered on the network.
 */
import { isPeerId } from '@libp2p/interface';
import { peerIdFromCID } from '@libp2p/peer-id';
import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record';
import all from 'it-all';
import { PersistentStore } from './store.js';
/**
 * An implementation of PeerStore that stores data in a Datastore
 */
class PersistentPeerStore {
    store;
    events;
    peerId;
    log;
    constructor(components, init = {}) {
        this.log = components.logger.forComponent('libp2p:peer-store');
        this.events = components.events;
        this.peerId = components.peerId;
        this.store = new PersistentStore(components, init);
    }
    [Symbol.toStringTag] = '@libp2p/peer-store';
    async forEach(fn, query) {
        for await (const peer of this.store.all(query)) {
            fn(peer);
        }
    }
    async all(query) {
        return all(this.store.all(query));
    }
    async delete(peerId, options) {
        const release = await this.store.getReadLock(peerId, options);
        try {
            await this.store.delete(peerId, options);
        }
        finally {
            release();
        }
    }
    async has(peerId, options) {
        const release = await this.store.getReadLock(peerId, options);
        try {
            return await this.store.has(peerId, options);
        }
        finally {
            this.log.trace('has release read lock');
            release?.();
        }
    }
    async get(peerId, options) {
        const release = await this.store.getReadLock(peerId, options);
        try {
            return await this.store.load(peerId, options);
        }
        finally {
            release?.();
        }
    }
    async getInfo(peerId, options) {
        const peer = await this.get(peerId, options);
        return {
            id: peer.id,
            multiaddrs: peer.addresses.map(({ multiaddr }) => multiaddr)
        };
    }
    async save(id, data, options) {
        const release = await this.store.getWriteLock(id, options);
        try {
            const result = await this.store.save(id, data, options);
            this.#emitIfUpdated(id, result);
            return result.peer;
        }
        finally {
            release?.();
        }
    }
    async patch(id, data, options) {
        const release = await this.store.getWriteLock(id, options);
        try {
            const result = await this.store.patch(id, data, options);
            this.#emitIfUpdated(id, result);
            return result.peer;
        }
        finally {
            release?.();
        }
    }
    async merge(id, data, options) {
        const release = await this.store.getWriteLock(id, options);
        try {
            const result = await this.store.merge(id, data, options);
            this.#emitIfUpdated(id, result);
            return result.peer;
        }
        finally {
            release?.();
        }
    }
    async consumePeerRecord(buf, arg1, arg2) {
        const expectedPeer = isPeerId(arg1) ? arg1 : isPeerId(arg1?.expectedPeer) ? arg1.expectedPeer : undefined;
        const options = isPeerId(arg1) ? arg2 : arg1 === undefined ? arg2 : arg1;
        const envelope = await RecordEnvelope.openAndCertify(buf, PeerRecord.DOMAIN, options);
        const peerId = peerIdFromCID(envelope.publicKey.toCID());
        if (expectedPeer?.equals(peerId) === false) {
            this.log('envelope peer id was not the expected peer id - expected: %p received: %p', expectedPeer, peerId);
            return false;
        }
        const peerRecord = PeerRecord.createFromProtobuf(envelope.payload);
        let peer;
        try {
            peer = await this.get(peerId, options);
        }
        catch (err) {
            if (err.name !== 'NotFoundError') {
                throw err;
            }
        }
        // ensure seq is greater than, or equal to, the last received
        if (peer?.peerRecordEnvelope != null) {
            const storedEnvelope = RecordEnvelope.createFromProtobuf(peer.peerRecordEnvelope);
            const storedRecord = PeerRecord.createFromProtobuf(storedEnvelope.payload);
            if (storedRecord.seqNumber >= peerRecord.seqNumber) {
                this.log('sequence number was lower or equal to existing sequence number - stored: %d received: %d', storedRecord.seqNumber, peerRecord.seqNumber);
                return false;
            }
        }
        await this.patch(peerRecord.peerId, {
            peerRecordEnvelope: buf,
            addresses: peerRecord.multiaddrs.map(multiaddr => ({
                isCertified: true,
                multiaddr
            }))
        }, options);
        return true;
    }
    #emitIfUpdated(id, result) {
        if (!result.updated) {
            return;
        }
        if (this.peerId.equals(id)) {
            this.events.safeDispatchEvent('self:peer:update', { detail: result });
        }
        else {
            this.events.safeDispatchEvent('peer:update', { detail: result });
        }
    }
}
export function persistentPeerStore(components, init = {}) {
    return new PersistentPeerStore(components, init);
}
//# sourceMappingURL=index.js.map