const t=require("b4a"),e=require("./errors"),s=require("./connection");function i(t,e){const s=t.peer.lower;return(s?t.timestamp:t.remoteTimestamp)>(s?e.timestamp:e.remoteTimestamp)?t:e}module.exports=class{constructor(e,s){this.id=t.toString(s,"hex"),this.lower=t.compare(e.publicKey,s)<0,this.swarm=e,this.publicKey=s,this.attempts=0,this.connectedAt=0,this.disconnectedAt=Date.now(),this.retryTimer=null,this.forceRelaying=!1,this.joined=!1,this.destroyed=!1,this.connection=null,this.connections=new Set,this._dialing=null}get stream(){return null===this.connection?null:this.connection.stream}get connected(){return null!==this.connection}internalDial(){if(!this.joined||this.destroyed)return;if(this.swarm.destroyed||this.swarm.suspended)return;if(null!==this.connection||null!==this._dialing)return;const t=this.swarm.dht.connect(this.publicKey,{keyPair:this.swarm.keyPair,relayThrough:this.swarm.relayThrough(this._shouldRelay())});this._dialing=this.internalAddConnection(t)}internalAddConnection(t){const e=new s(this.swarm,this,t);return this.connections.add(e),e.on("close",()=>this._onclose(e)),e.on("established",()=>this._update()),e}internalClearRetry(){null!==this.retryTimer&&(clearTimeout(this.retryTimer),this.retryTimer=null)}internalCloseConnections(t){const e=[];for(const s of this.connections)e.push(new Promise(t=>s.once("close",t))),this._close(s,t);return Promise.all(e)}internalDestroy(t){return this.destroyed?Promise.resolve([]):(this.destroyed=!0,this.internalClearRetry(),this.internalCloseConnections(t))}internalRelease(){if(!this.destroyed&&this.joined){this.joined=!1,this.internalClearRetry();for(const t of this.connections)t!==this.connection&&this._close(t,e.Abandoned());null===this.connection&&(this.destroyed=!0)}}_scheduleRetry(){if(!this.joined||this.destroyed)return;if(this.swarm.destroyed||this.swarm.suspended)return;if(null!==this.retryTimer)return;if(null!==this.connection||null!==this._dialing)return;const{retryBackoffs:t,retryJitter:e}=this.swarm.timings,s=t[Math.min(this.attempts,t.length-1)];this.attempts++,this.retryTimer=setTimeout(()=>{this.retryTimer=null,this.internalDial()},s+Math.round(Math.random()*e))}_shouldRelay(){const t=this.swarm.timings;return!!this.forceRelaying||this.attempts>=t.relayAfterAttempts||Date.now()-this.disconnectedAt>=t.relayAfter}_best(){let t=null;for(const e of this.connections)e.established&&(t=null===t?e:i(t,e));return t}_update(){if(this.destroyed||this.swarm.destroyed||this.swarm.suspended)return;const t=this._best();if(null!==t){for(const s of this.connections)s!==t&&s.established&&this._close(s,e.Duplicate());null===this.connection&&this._expose(t)}}_close(t,e){this.connections.delete(t),t.internalDestroy(e)}_expose(t){this.connection=t,this.connectedAt=Date.now(),this.swarm.internalAddConnection(t.stream,this)}_resolve(){this.destroyed||this.swarm.destroyed||this.swarm.suspended||(null===this._best()?this._scheduleRetry():queueMicrotask(()=>this._update()))}_onclose(t){this.connections.delete(t),this._dialing===t&&(this._dialing=null),this.connection===t&&(this.connection=null,this.disconnectedAt=Date.now(),Date.now()-this.connectedAt>=this.swarm.timings.stableConnection&&(this.attempts=0,this.forceRelaying=!1),this.swarm.internalRemoveConnection(t.stream,this,t.error)),this.joined||0!==this.connections.size||(this.destroyed=!0,this.swarm.internalRemovePeer(this)),this._resolve()}};