const{EventEmitter:e}=require("events"),t=require("hyperdht"),s=require("b4a"),i=require("./lib/peer"),r=require("./lib/errors"),n={keepAlive:1e3,livenessTick:500,idleTimeout:3e3,establishTimeout:3e3,stableConnection:5e3,relayAfter:8e3,relayAfterAttempts:3,retryBackoffs:[500,1e3,2e3],retryJitter:250},o={fallback:[200,400,800],min:150,max:1e3,jitter:64};function h(){}function a(){return!0}function d(){return null}module.exports=class extends e{constructor(e={}){super();const{dht:s,seed:i,nodes:r,testing:l,firewall:u,bootstrap:c,relayThrough:p,dhtOpts:y={},keyPair:m=t.keyPair(i)}=e;this.dht=s||new t({bootstrap:c,nodes:r,adaptiveTimeout:o,...y}),this.keyPair=m,this.timings={...n,...l},this.firewall=u||a,this.relaying=!!p,this.relayThrough=function(e,t){return e?"function"==typeof e?s=>e(s,t):s=>s||t.dht.randomized?e:null:d}(p,this),this.peers=new Map,this.destroyed=!1,this.suspended=!1,this.connections=new Set,this._ownsDHT=!s,this._timestamp=0,this._onnetworkupdate=this._handleNetworkUpdate.bind(this),this.server=this.dht.createServer({firewall:this._handleFirewall.bind(this),relayThrough:this.relayThrough},this._onincoming.bind(this)),this.listening=this.server.listen(this.keyPair),this.listening.catch(h),this.dht.on("network-update",this._onnetworkupdate)}get publicKey(){return this.keyPair.publicKey}ready(){return this.listening}joinPeer(e){if(this.destroyed)throw r.Destroyed();if(this.suspended)throw r.Suspended();if(s.equals(e,this.publicKey))return null;const t=this._upsertPeer(e);return t.joined=!0,t.internalDial(),t}leavePeer(e){if(this.destroyed)throw r.Destroyed();if(this.suspended)throw r.Suspended();const t=s.toString(e,"hex"),i=this.peers.get(t);i&&(i.internalRelease(),null===i.connection&&this.peers.delete(t))}async destroy(){if(this.destroyed)return;this.destroyed=!0,this.dht.removeListener("network-update",this._onnetworkupdate);const e=[];for(const t of this.peers.values())e.push(t.internalDestroy(null));this.peers.clear(),await this.server.close(),await Promise.all(e),this._ownsDHT&&await this.dht.destroy()}async suspend(){if(this.suspended||this.destroyed)return;this.suspended=!0;const e=[];for(const t of this.peers.values())t.internalClearRetry(),e.push(t.internalCloseConnections(null));this._ownsDHT?await this.dht.suspend():await this.server.suspend(),await Promise.all(e),this.emit("suspend")}async resume(){if(this.suspended&&!this.destroyed){this.suspended=!1,this._ownsDHT?await this.dht.resume():await this.server.resume();for(const e of this.peers.values())e.internalClearRetry(),e.attempts=0,e.disconnectedAt=Date.now(),e.internalDial();this.emit("resume")}}internalNextTimestamp(){const e=Date.now();return e>this._timestamp?this._timestamp=e:this._timestamp++,this._timestamp}internalAddConnection(e,t){this.connections.add(e),this.emit("connection",e,t)}internalRemoveConnection(e,t,s){this.connections.delete(e),this.emit("disconnection",e,t,s)}internalRemovePeer(e){this.peers.get(e.id)===e&&this.peers.delete(e.id)}async _handleFirewall(e,t){if(this.destroyed)return!0;if(s.equals(e,this.publicKey))return!0;const i=this.peers.get(s.toString(e,"hex"));if(i&&i.joined)return!1;try{return await this.firewall(e,t)}catch{return!0}}_onincoming(e){if(this.destroyed||this.suspended)return e.on("error",h),void e.destroy(this.destroyed?r.Destroyed():r.Suspended());const t=this._upsertPeer(e.remotePublicKey);t.joined=!0,t.internalAddConnection(e)}_upsertPeer(e){const t=s.toString(e,"hex");let r=this.peers.get(t);return r||(r=new i(this,e),this.peers.set(t,r)),r}_handleNetworkUpdate(){if(!this.destroyed&&!this.suspended){this.server.refresh();for(const e of this.connections)e.sendKeepAlive();for(const e of this.peers.values())null!==e.retryTimer&&(e.internalClearRetry(),e.internalDial())}}};