/* eslint-disable complexity */
import { publicKeyToProtobuf } from '@libp2p/crypto/keys';
import { InvalidParametersError } from '@libp2p/interface';
import { equals as uint8ArrayEquals } from 'uint8arrays/equals';
import { dedupeFilterAndSortAddresses } from './dedupe-addresses.js';
export async function toPeerPB(peerId, data, strategy, options) {
    if (data == null) {
        throw new InvalidParametersError('Invalid PeerData');
    }
    if (data.publicKey != null && peerId.publicKey != null && !data.publicKey.equals(peerId.publicKey)) {
        throw new InvalidParametersError('publicKey bytes do not match peer id publicKey bytes');
    }
    const existingPeer = options.existingPeer?.peer;
    if (existingPeer != null && !peerId.equals(existingPeer.id)) {
        throw new InvalidParametersError('peer id did not match existing peer id');
    }
    let addresses = existingPeer?.addresses ?? [];
    let protocols = new Set(existingPeer?.protocols ?? []);
    let metadata = existingPeer?.metadata ?? new Map();
    let tags = existingPeer?.tags ?? new Map();
    let peerRecordEnvelope = existingPeer?.peerRecordEnvelope;
    // when patching, we replace the original fields with passed values
    if (strategy === 'patch') {
        if (data.multiaddrs != null || data.addresses != null) {
            addresses = [];
            if (data.multiaddrs != null) {
                addresses.push(...data.multiaddrs.map(multiaddr => ({
                    isCertified: false,
                    multiaddr
                })));
            }
            if (data.addresses != null) {
                addresses.push(...data.addresses);
            }
        }
        if (data.protocols != null) {
            protocols = new Set(data.protocols);
        }
        if (data.metadata != null) {
            const metadataEntries = data.metadata instanceof Map ? [...data.metadata.entries()] : Object.entries(data.metadata);
            metadata = createSortedMap(metadataEntries, {
                validate: validateMetadata
            });
        }
        if (data.tags != null) {
            const tagsEntries = data.tags instanceof Map ? [...data.tags.entries()] : Object.entries(data.tags);
            tags = createSortedMap(tagsEntries, {
                validate: validateTag,
                map: mapTag
            });
        }
        if (data.peerRecordEnvelope != null) {
            peerRecordEnvelope = data.peerRecordEnvelope;
        }
    }
    // when merging, we join the original fields with passed values
    if (strategy === 'merge') {
        if (data.multiaddrs != null) {
            addresses.push(...data.multiaddrs.map(multiaddr => ({
                isCertified: false,
                multiaddr
            })));
        }
        if (data.addresses != null) {
            addresses.push(...data.addresses);
        }
        if (data.protocols != null) {
            protocols = new Set([...protocols, ...data.protocols]);
        }
        if (data.metadata != null) {
            const metadataEntries = data.metadata instanceof Map ? [...data.metadata.entries()] : Object.entries(data.metadata);
            for (const [key, value] of metadataEntries) {
                if (value == null) {
                    metadata.delete(key);
                }
                else {
                    metadata.set(key, value);
                }
            }
            metadata = createSortedMap([...metadata.entries()], {
                validate: validateMetadata
            });
        }
        if (data.tags != null) {
            const tagsEntries = data.tags instanceof Map ? [...data.tags.entries()] : Object.entries(data.tags);
            const mergedTags = new Map(tags);
            for (const [key, value] of tagsEntries) {
                if (value == null) {
                    mergedTags.delete(key);
                }
                else {
                    mergedTags.set(key, value);
                }
            }
            tags = createSortedMap([...mergedTags.entries()], {
                validate: validateTag,
                map: mapTag
            });
        }
        if (data.peerRecordEnvelope != null) {
            peerRecordEnvelope = data.peerRecordEnvelope;
        }
    }
    let publicKey;
    if (existingPeer?.id.publicKey != null) {
        publicKey = publicKeyToProtobuf(existingPeer.id.publicKey);
    }
    else if (data.publicKey != null) {
        publicKey = publicKeyToProtobuf(data.publicKey);
    }
    else if (peerId.publicKey != null) {
        publicKey = publicKeyToProtobuf(peerId.publicKey);
    }
    const output = {
        addresses: await dedupeFilterAndSortAddresses(peerId, options.addressFilter ?? (async () => true), addresses, options.existingPeer?.peerPB.addresses, options),
        protocols: [...protocols.values()].sort((a, b) => {
            return a.localeCompare(b);
        }),
        metadata,
        tags,
        publicKey,
        peerRecordEnvelope
    };
    // add observed addresses to multiaddrs
    output.addresses.forEach(addr => {
        addr.observed = options.existingPeer?.peerPB.addresses?.find(addr => uint8ArrayEquals(addr.multiaddr, addr.multiaddr))?.observed ?? Date.now();
    });
    // Ed25519 and secp256k1 have their public key embedded in them so no need to duplicate it
    if (peerId.type !== 'RSA') {
        delete output.publicKey;
    }
    return output;
}
/**
 * In JS maps are ordered by insertion order so create a new map with the
 * keys inserted in alphabetical order.
 */
function createSortedMap(entries, options) {
    const output = new Map();
    for (const [key, value] of entries) {
        if (value == null) {
            continue;
        }
        options.validate(key, value);
    }
    for (const [key, value] of entries.sort(([a], [b]) => {
        return a.localeCompare(b);
    })) {
        if (value != null) {
            output.set(key, options.map?.(key, value) ?? value);
        }
    }
    return output;
}
function validateMetadata(key, value) {
    if (typeof key !== 'string') {
        throw new InvalidParametersError('Metadata key must be a string');
    }
    if (!(value instanceof Uint8Array)) {
        throw new InvalidParametersError('Metadata value must be a Uint8Array');
    }
}
function validateTag(key, tag) {
    if (typeof key !== 'string') {
        throw new InvalidParametersError('Tag name must be a string');
    }
    if (tag.value != null) {
        if (parseInt(`${tag.value}`, 10) !== tag.value) {
            throw new InvalidParametersError('Tag value must be an integer');
        }
        if (tag.value < 0 || tag.value > 100) {
            throw new InvalidParametersError('Tag value must be between 0-100');
        }
    }
    if (tag.ttl != null) {
        if (parseInt(`${tag.ttl}`, 10) !== tag.ttl) {
            throw new InvalidParametersError('Tag ttl must be an integer');
        }
        if (tag.ttl < 0) {
            throw new InvalidParametersError('Tag ttl must be between greater than 0');
        }
    }
}
function mapTag(key, tag) {
    let expiry;
    if (tag.expiry != null) {
        expiry = tag.expiry;
    }
    if (tag.ttl != null) {
        expiry = BigInt(Date.now() + Number(tag.ttl));
    }
    const output = {
        value: tag.value ?? 0
    };
    if (expiry != null) {
        output.expiry = expiry;
    }
    return output;
}
//# sourceMappingURL=to-peer-pb.js.map