"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.IntegerObj = exports.NullObj = exports.ExternFnObj = exports.EnumValueObj = exports.RefObj = exports.PromiseObj = exports.ChannelObj = exports.NexusRuntime = void 0;
exports.isChannel = isChannel;
exports.isPromiseObj = isPromiseObj;
exports.isRefObj = isRefObj;
exports.isEnumValue = isEnumValue;
exports.asInteger = asInteger;
exports.runtimeError = runtimeError;
const values_1 = require("./values");
Object.defineProperty(exports, "IntegerObj", { enumerable: true, get: function () { return values_1.IntegerObj; } });
Object.defineProperty(exports, "NullObj", { enumerable: true, get: function () { return values_1.NullObj; } });
/**
 * Lightweight cooperative async scheduler with green tasks and channels.
 */
class NexusRuntime {
    nextTaskId = 1;
    ready = [];
    running = false;
    pending = new Set();
    /**
     * Spawn a green task. Returns a handle whose `done` promise resolves when
     * the task body finishes.
     */
    spawn(fn) {
        const id = this.nextTaskId++;
        let resolveDone;
        const done = new Promise((resolve) => {
            resolveDone = resolve;
        });
        const run = async () => {
            try {
                await fn();
            }
            catch {
                // Task errors are isolated.
            }
            finally {
                resolveDone();
            }
        };
        const promise = run();
        this.pending.add(promise);
        void promise.finally(() => this.pending.delete(promise));
        return { id, done };
    }
    /** Yield to the event loop (cooperative scheduling point). */
    async yield() {
        await new Promise((resolve) => setImmediate(resolve));
    }
    /** Drain until all spawned tasks and pending work settle. */
    async drain() {
        if (this.running) {
            return;
        }
        this.running = true;
        try {
            // Allow newly spawned work to register.
            await this.yield();
            let spins = 0;
            while ((this.pending.size > 0 || this.ready.length > 0) && spins < 100000) {
                spins += 1;
                while (this.ready.length > 0) {
                    const job = this.ready.shift();
                    job();
                }
                if (this.pending.size === 0) {
                    break;
                }
                await Promise.race([
                    Promise.allSettled([...this.pending]),
                    new Promise((r) => setImmediate(r)),
                ]);
            }
        }
        finally {
            this.running = false;
        }
    }
    enqueue(job) {
        this.ready.push(job);
    }
    createChannel(capacity = 0) {
        return new ChannelObj(this, Math.max(0, capacity));
    }
}
exports.NexusRuntime = NexusRuntime;
/**
 * Message-passing channel. Capacity 0 is unbuffered (rendezvous);
 * capacity > 0 is a bounded buffer.
 */
class ChannelObj {
    runtime;
    capacity;
    type = "CHANNEL";
    buffer = [];
    sendWaiters = [];
    recvWaiters = [];
    closed = false;
    constructor(runtime, capacity) {
        this.runtime = runtime;
        this.capacity = capacity;
    }
    inspect() {
        return `chan(capacity=${this.capacity}, len=${this.buffer.length}, closed=${this.closed})`;
    }
    async send(value) {
        if (this.closed) {
            throw new Error("send on closed channel");
        }
        if (this.recvWaiters.length > 0) {
            const waiter = this.recvWaiters.shift();
            waiter.resolve(value);
            await this.runtime.yield();
            return;
        }
        if (this.buffer.length < this.capacity) {
            this.buffer.push(value);
            await this.runtime.yield();
            return;
        }
        await new Promise((resolve, reject) => {
            this.sendWaiters.push({ value, resolve, reject });
        });
        await this.runtime.yield();
    }
    async recv() {
        if (this.buffer.length > 0) {
            const value = this.buffer.shift();
            if (this.sendWaiters.length > 0 && this.buffer.length < this.capacity) {
                const sender = this.sendWaiters.shift();
                this.buffer.push(sender.value);
                sender.resolve();
            }
            await this.runtime.yield();
            return value;
        }
        if (this.sendWaiters.length > 0) {
            const sender = this.sendWaiters.shift();
            sender.resolve();
            await this.runtime.yield();
            return sender.value;
        }
        if (this.closed) {
            return values_1.NULL_OBJ;
        }
        const value = await new Promise((resolve, reject) => {
            this.recvWaiters.push({ resolve, reject });
        });
        await this.runtime.yield();
        return value;
    }
    close() {
        this.closed = true;
        while (this.sendWaiters.length > 0) {
            const s = this.sendWaiters.shift();
            s.reject(new Error("send on closed channel"));
        }
        while (this.recvWaiters.length > 0) {
            const r = this.recvWaiters.shift();
            r.resolve(values_1.NULL_OBJ);
        }
    }
}
exports.ChannelObj = ChannelObj;
/** Runtime-backed promise wrapper for async Nexus values. */
class PromiseObj {
    promise;
    type = "PROMISE";
    constructor(promise) {
        this.promise = promise;
    }
    inspect() {
        return "promise";
    }
    async awaitValue() {
        return this.promise;
    }
}
exports.PromiseObj = PromiseObj;
class RefObj {
    target;
    mutable;
    type = "REF";
    constructor(target, mutable) {
        this.target = target;
        this.mutable = mutable;
    }
    inspect() {
        return `${this.mutable ? "&mut " : "&"}${this.target.inspect()}`;
    }
}
exports.RefObj = RefObj;
class EnumValueObj {
    enumName;
    variant;
    fields;
    type = "ENUM_VALUE";
    constructor(enumName, variant, fields) {
        this.enumName = enumName;
        this.variant = variant;
        this.fields = fields;
    }
    inspect() {
        const prefix = this.enumName ? `${this.enumName}::` : "";
        if (this.fields.length === 0) {
            return `${prefix}${this.variant}`;
        }
        return `${prefix}${this.variant}(${this.fields.map((f) => f.inspect()).join(", ")})`;
    }
}
exports.EnumValueObj = EnumValueObj;
class ExternFnObj {
    library;
    symbol;
    returnType;
    call;
    type = "EXTERN_FN";
    constructor(library, symbol, returnType, call) {
        this.library = library;
        this.symbol = symbol;
        this.returnType = returnType;
        this.call = call;
    }
    inspect() {
        return `extern fn ${this.symbol} from ${JSON.stringify(this.library)}`;
    }
}
exports.ExternFnObj = ExternFnObj;
function isChannel(obj) {
    return obj instanceof ChannelObj || obj.type === "CHANNEL";
}
function isPromiseObj(obj) {
    return obj instanceof PromiseObj || obj.type === "PROMISE";
}
function isRefObj(obj) {
    return obj instanceof RefObj || obj.type === "REF";
}
function isEnumValue(obj) {
    return obj instanceof EnumValueObj || obj.type === "ENUM_VALUE";
}
function asInteger(obj) {
    if (obj instanceof values_1.IntegerObj) {
        return obj.value;
    }
    return null;
}
function runtimeError(message) {
    return (0, values_1.newError)(message);
}
//# sourceMappingURL=runtime.js.map