import { CancelledError, RestateError, TerminalError, TimeoutError } from "./types/errors.js";
import { CompletablePromise } from "./utils/completable_promise.js";
import { setImmediate } from "node:timers/promises";

//#region src/promises.ts
function pendingPromise() {
	return new Promise(() => {});
}
/**
* Returns `true` if the given value is a {@link RestatePromise}.
*
* Use this for runtime type detection when you need to distinguish Restate promises
* from regular promises, e.g. for overload resolution.
*/
function isRestatePromise(p) {
	return p instanceof InternalRestatePromise;
}
var PromiseState = /* @__PURE__ */ function(PromiseState$1) {
	PromiseState$1[PromiseState$1["COMPLETED"] = 0] = "COMPLETED";
	PromiseState$1[PromiseState$1["NOT_COMPLETED"] = 1] = "NOT_COMPLETED";
	return PromiseState$1;
}(PromiseState || {});
var InternalRestatePromise = class {};
const RESTATE_CTX_SYMBOL = Symbol("restateContext");
function extractContext(n) {
	return n[RESTATE_CTX_SYMBOL];
}
var BaseRestatePromise = class extends InternalRestatePromise {
	[RESTATE_CTX_SYMBOL];
	pollingPromise;
	cancelPromise = new CompletablePromise();
	constructor(ctx) {
		super();
		this[RESTATE_CTX_SYMBOL] = ctx;
	}
	then(onfulfilled, onrejected) {
		this.pollingPromise = this.pollingPromise || this[RESTATE_CTX_SYMBOL].promisesExecutor.doProgress(this).catch(() => {});
		return this.publicPromiseOrCancelPromise().then(onfulfilled, onrejected);
	}
	catch(onrejected) {
		this.pollingPromise = this.pollingPromise || this[RESTATE_CTX_SYMBOL].promisesExecutor.doProgress(this).catch(() => {});
		return this.publicPromiseOrCancelPromise().catch(onrejected);
	}
	finally(onfinally) {
		this.pollingPromise = this.pollingPromise || this[RESTATE_CTX_SYMBOL].promisesExecutor.doProgress(this).catch(() => {});
		return this.publicPromiseOrCancelPromise().finally(onfinally);
	}
	publicPromiseOrCancelPromise() {
		return Promise.race([this.cancelPromise.promise, this.publicPromise()]);
	}
	orTimeout(duration) {
		return new CombinatorRestatePromise(this[RESTATE_CTX_SYMBOL], ([thisPromise, sleepPromise]) => {
			return new Promise((resolve, reject) => {
				thisPromise.then(resolve, reject);
				sleepPromise.then(() => {
					reject(new TimeoutError());
				}, reject);
			});
		}, [this, this[RESTATE_CTX_SYMBOL].sleep(duration)]);
	}
	map(mapper) {
		return new MappedRestatePromise(this[RESTATE_CTX_SYMBOL], this, mapper);
	}
	tryCancel() {
		this.cancelPromise.reject(new CancelledError());
	}
};
var SingleRestatePromise = class extends BaseRestatePromise {
	state = PromiseState.NOT_COMPLETED;
	completablePromise = new CompletablePromise();
	constructor(ctx, handle, completer) {
		super(ctx);
		this.handle = handle;
		this.completer = completer;
	}
	uncompletedLeaves() {
		return this.state === PromiseState.COMPLETED ? [] : [this.handle];
	}
	async tryComplete() {
		if (this.state === PromiseState.COMPLETED) return;
		const notification = this[RESTATE_CTX_SYMBOL].coreVm.take_notification(this.handle);
		if (notification === "NotReady") return;
		this.state = PromiseState.COMPLETED;
		await this.completer(notification, this.completablePromise);
	}
	publicPromise() {
		return this.completablePromise.promise;
	}
	isCompleted() {
		return this.state === PromiseState.COMPLETED;
	}
	[Symbol.toStringTag] = "RestateSinglePromise";
};
var InvocationRestatePromise = class extends SingleRestatePromise {
	constructor(ctx, handle, completer, invocationIdPromise) {
		super(ctx, handle, completer);
		this.invocationIdPromise = invocationIdPromise;
	}
	get invocationId() {
		return this.invocationIdPromise;
	}
};
var CombinatorRestatePromise = class CombinatorRestatePromise extends BaseRestatePromise {
	state = PromiseState.NOT_COMPLETED;
	combinatorPromise;
	constructor(ctx, combinatorConstructor, childs) {
		super(ctx);
		this.childs = childs;
		this.combinatorPromise = combinatorConstructor(childs.map((p) => p.publicPromise())).finally(() => {
			this.state = PromiseState.COMPLETED;
		});
	}
	static fromPromises(combinatorConstructor, promises) {
		const castedPromises = [];
		let foundContext = void 0;
		for (const [idx, promise] of promises.entries()) {
			if (!isRestatePromise(promise)) throw new Error(`Promise index ${idx} used inside the combinator is not an instance of RestatePromise. This is not supported.`);
			else if (foundContext === void 0) foundContext = extractContext(promise);
			else {
				const thisContext = extractContext(promise);
				if (thisContext !== void 0 && thisContext !== foundContext) throw new Error("You're mixing up RestatePromises from different RestateContext. This is not supported.");
			}
			castedPromises.push(promise);
		}
		if (foundContext === void 0) return ConstRestatePromise.fromPromise(combinatorConstructor(castedPromises), true);
		return new CombinatorRestatePromise(foundContext, combinatorConstructor, castedPromises);
	}
	uncompletedLeaves() {
		return this.state === PromiseState.COMPLETED ? [] : this.childs.flatMap((p) => p.uncompletedLeaves());
	}
	async tryComplete() {
		await Promise.allSettled(this.childs.map((c) => c.tryComplete()));
	}
	publicPromise() {
		return this.combinatorPromise;
	}
	[Symbol.toStringTag] = "RestateCombinatorPromise";
};
var MappedRestatePromise = class extends BaseRestatePromise {
	publicPromiseMapper;
	_mappedPromise;
	constructor(ctx, inner, mapper) {
		super(ctx);
		this.inner = inner;
		this.publicPromiseMapper = (value, failure) => {
			try {
				return Promise.resolve(mapper(value, failure));
			} catch (e) {
				if (e instanceof TerminalError) return Promise.reject(e);
				else {
					ctx.abortAttempt(e);
					return pendingPromise();
				}
			}
		};
	}
	async tryComplete() {
		await this.inner.tryComplete();
	}
	uncompletedLeaves() {
		return this.inner.uncompletedLeaves();
	}
	publicPromise() {
		return this._mappedPromise ??= this.buildMappedPromise();
	}
	buildMappedPromise() {
		const promiseMapper = this.publicPromiseMapper;
		return this.inner.publicPromise().then((t) => promiseMapper(t, void 0), (error) => {
			if (error instanceof RestateError) return promiseMapper(void 0, error);
			else throw error;
		});
	}
	[Symbol.toStringTag] = "RestateMappedPromise";
};
var ConstRestatePromise = class ConstRestatePromise extends InternalRestatePromise {
	_constPromise;
	constructor(promiseFactory, settled) {
		super();
		this.promiseFactory = promiseFactory;
		this.settled = settled;
	}
	get constPromise() {
		return this._constPromise ??= this.promiseFactory();
	}
	static resolve(value) {
		return new ConstRestatePromise(() => Promise.resolve(value), true);
	}
	static reject(reason) {
		return new ConstRestatePromise(() => Promise.reject(reason), true);
	}
	static pending() {
		return new ConstRestatePromise(() => pendingPromise(), false);
	}
	static fromPromise(promise, settled) {
		return new ConstRestatePromise(() => promise, settled);
	}
	then(onfulfilled, onrejected) {
		return this.constPromise.then(onfulfilled, onrejected);
	}
	catch(onrejected) {
		return this.constPromise.catch(onrejected);
	}
	finally(onfinally) {
		return this.constPromise.finally(onfinally);
	}
	orTimeout() {
		if (this.settled) return this;
		return ConstRestatePromise.reject(new TimeoutError());
	}
	map(mapper) {
		if (!this.settled) return this;
		const selfConstPromise = this.constPromise;
		return new ConstRestatePromise(() => selfConstPromise.then((value) => mapper(value, void 0), (reason) => mapper(void 0, reason)), this.settled);
	}
	tryCancel() {}
	publicPromise() {
		return this.constPromise;
	}
	tryComplete() {
		return Promise.resolve();
	}
	uncompletedLeaves() {
		return [];
	}
	[Symbol.toStringTag] = "ConstRestatePromise";
};
/**
* Promises executor, gluing VM with I/O and Promises given to user space.
*/
var PromisesExecutor = class {
	constructor(coreVm, inputPump, outputPump, runClosuresTracker, errorCallback) {
		this.coreVm = coreVm;
		this.inputPump = inputPump;
		this.outputPump = outputPump;
		this.runClosuresTracker = runClosuresTracker;
		this.errorCallback = errorCallback;
	}
	async doProgress(restatePromise) {
		await this.outputPump.awaitNextProgress();
		await this.doProgressInner(restatePromise);
	}
	async doProgressInner(restatePromise) {
		try {
			await restatePromise.tryComplete();
		} catch (e) {
			this.errorCallback(e);
			return Promise.resolve();
		}
		return setImmediate().then(async () => {
			try {
				const handles = restatePromise.uncompletedLeaves();
				if (handles.length === 0) return;
				const doProgressResult = this.coreVm.do_progress(new Uint32Array(handles));
				if (doProgressResult === "AnyCompleted") {} else if (doProgressResult === "ReadFromInput" || doProgressResult === "WaitingPendingRun") {
					await this.outputPump.awaitNextProgress();
					await Promise.race([this.inputPump.awaitNextProgress(), this.runClosuresTracker.awaitNextCompletedRun()]);
				} else if (doProgressResult === "CancelSignalReceived") {
					restatePromise.tryCancel();
					return;
				} else {
					this.runClosuresTracker.executeRun(doProgressResult.ExecuteRun);
					await setImmediate();
				}
				await this.doProgressInner(restatePromise);
			} catch (e) {
				this.errorCallback(e);
			}
		});
	}
};

//#endregion
export { CombinatorRestatePromise, ConstRestatePromise, InternalRestatePromise, InvocationRestatePromise, PromisesExecutor, SingleRestatePromise, isRestatePromise, pendingPromise };
//# sourceMappingURL=promises.js.map