'use strict'

const errCode = require('err-code')
const filter = require('it-filter')
const map = require('it-map')
const take = require('it-take')

/**
 * @typedef {import('peer-id')} PeerId
 * @typedef {import('multiaddr').Multiaddr} Multiaddr
 */

/**
 * Store the multiaddrs from every peer in the passed peer store
 *
 * @param {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} source
 * @param {import('../peer-store/types').PeerStore} peerStore
 */
async function * storeAddresses (source, peerStore) {
  yield * map(source, async (peer) => {
    // ensure we have the addresses for a given peer
    await peerStore.addressBook.add(peer.id, peer.multiaddrs)

    return peer
  })
}

/**
 * Filter peers by unique peer id
 *
 * @param {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} source
 */
function uniquePeers (source) {
  /** @type Set<string> */
  const seen = new Set()

  return filter(source, (peer) => {
    // dedupe by peer id
    if (seen.has(peer.id.toString())) {
      return false
    }

    seen.add(peer.id.toString())

    return true
  })
}

/**
 * Require at least `min` peers to be yielded from `source`
 *
 * @param {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} source
 * @param {number} min
 */
async function * requirePeers (source, min = 1) {
  let seen = 0

  for await (const peer of source) {
    seen++

    yield peer
  }

  if (seen < min) {
    throw errCode(new Error('not found'), 'NOT_FOUND')
  }
}

/**
 * If `max` is passed, only take that number of peers from the source
 * otherwise take all the peers
 *
 * @param {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} source
 * @param {number} [max]
 */
function maybeLimitSource (source, max) {
  if (max) {
    return take(source, max)
  }

  return source
}

module.exports = {
  storeAddresses,
  uniquePeers,
  requirePeers,
  maybeLimitSource
}
