/* COPYRIGHT Esri - https://js.arcgis.com/5.1.0/LICENSE.txt */
import{getOrCreateMapValue as e}from"../core/MapUtils.js";import{settleWithPromise as t,createAbortError as s}from"../core/promiseUtils.js";import{QueueProcessor as i}from"../core/QueueProcessor.js";function r(e,t,s,i){return new o(e,t,s,i)}class n{constructor(e,t,s){this._executor=e,this._data=t,this._impl=s,this._closed=!1,this._resolver=Promise.withResolvers(),this._impl.open(this)}assertOpen(){if(this._closed)throw new Error("Batch closed.")}get data(){return this.assertOpen(),this._data}get results(){return this._resolver.promise}send(){try{this.assertOpen(),this._closed=!0,this._executor.resume(this),this._impl.close(this),t(this._resolver,this._impl.execute(this._data))}catch(e){this._resolver.reject(e)}}}class o{constructor(e,s,r,n){if(this._runJob=s,this._maxRunning=r,this._abortSignal=n,this._jobIdSeq=0,this._running=new Set,this._blocked=new Set,this._openBatches=new Map,this._state="ready",this._runResolver=null,r<1)throw new Error(`_maxRunning=${r} but cannot be < 1`);this._todo=e[Symbol.iterator](),this._queue=new i({concurrency:1,process:async e=>{if("start"===e.type){const{id:s,args:i}=e,r=Promise.withResolvers(),n=Promise.withResolvers(),o={id:s,interrupt:r,finished:n.promise};return t(n,this._runJob(i,{id:s,yieldFor:e=>this.yieldFor(o,e)}).then(()=>{this._blocked.has(s)&&this._fail(new Error(`job ${s} completed before resuming`)),this._running.delete(s)||this._fail(new Error(`job ${s} not running?`))})),await Promise.race([r.promise,o.finished]),void this._tryQueue()}if("continue"===e.type)return e.job.interrupt=Promise.withResolvers(),"fulfilled"===e.data.status?e.continuation.resolve(e.data.value):e.continuation.reject(e.data.reason),await Promise.race([e.job.interrupt.promise,e.job.finished]),void this._tryQueue()}})}openBatch(e,t){return new n(this,e,t)}_fail(e,t=!1){if(this._runResolver?.reject(e),this._state="stopped",!t)throw e}_schedule(e){this._queue.push(e).catch(e=>this._fail(e,!0))}_tryQueue(){if("running"===this._state)if(this._abortSignal?.aborted)this._fail(s(),!0);else{if(this._running.size<this._maxRunning){const e=this._todo.next();if(!e.done){const t=this._jobIdSeq++;return this._running.add(t),void this._schedule({type:"start",id:t,args:e.value})}}if(0===this._running.size)return this._runResolver?.resolve(),this._runResolver=null,void(this._state="stopped");if(this._blocked.size===this._running.size){let e=null,t=-1/0;for(const[s,i]of this._openBatches)i.size>t&&(e=s,t=i.size);return null==e&&this._fail(new Error("deadlock")),void e.send()}}}resume(e){const t=this._openBatches.get(e);if(this._openBatches.delete(e),null!=t)for(const s of t)this._blocked.delete(s)||this._fail(new Error(`job ${s} not suspended`))}async yieldFor(t,s){s.assertOpen(),this._running.has(t.id)||this._fail(new Error(`job ${t.id} not running`)),this._blocked.has(t.id)&&this._fail(new Error(`job ${t.id} already suspended`)),this._blocked.add(t.id),e(this._openBatches,s,()=>new Set).add(t.id),null==t.interrupt&&this._fail(new Error(`job ${t.id} hasn't resumed yet`)),t.interrupt.resolve(),t.interrupt=null;const i=Promise.withResolvers();let r;try{r={status:"fulfilled",value:await s.results}}catch(n){r={status:"rejected",reason:n}}return this._schedule({type:"continue",continuation:i,data:r,job:t}),i.promise}run(){"ready"!==this._state&&this._fail(new Error(`executor not ready to start. state=${this._state}`));const e=Promise.withResolvers();return this._state="running",this._runResolver=e,this._tryQueue(),e.promise}}export{r as createBatchExecutor};
