"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MemoryEngine = exports.Arc = exports.Rc = void 0;
exports.getMemoryEngine = getMemoryEngine;
exports.resetMemoryEngine = resetMemoryEngine;
const values_1 = require("../language/values");
/** Shared ownership smart pointer (single-threaded Rc). */
class Rc {
    heap;
    id;
    constructor(heap, id) {
        this.heap = heap;
        this.id = id;
        this.heap.retain(id);
    }
    get() {
        return this.heap.deref(this.id);
    }
    clone() {
        return new Rc(this.heap, this.id);
    }
    drop() {
        this.heap.release(this.id);
    }
}
exports.Rc = Rc;
/** Atomic-style shared pointer API (cooperative Arc in the extension host). */
class Arc {
    heap;
    id;
    lock = { taken: false };
    constructor(heap, id) {
        this.heap = heap;
        this.id = id;
        this.heap.retain(id);
    }
    get() {
        return this.heap.deref(this.id);
    }
    clone() {
        return new Arc(this.heap, this.id);
    }
    /** Cooperative critical section for host-thread safety. */
    withLock(fn) {
        if (this.lock.taken) {
            throw new Error("Arc lock contention");
        }
        this.lock.taken = true;
        try {
            return fn(this.get());
        }
        finally {
            this.lock.taken = false;
        }
    }
    drop() {
        this.heap.release(this.id);
    }
}
exports.Arc = Arc;
/**
 * Heap with strong/weak refcounts and incremental cycle detection
 * (Bacon–Rajan style purple buffering).
 */
class MemoryEngine {
    nextId = 1;
    objects = new Map();
    cycleBuffer = [];
    collectBudget = 32;
    freed = 0;
    allocated = 0;
    stats() {
        return {
            allocated: this.allocated,
            live: this.objects.size,
            freed: this.freed,
            buffered: this.cycleBuffer.length,
        };
    }
    alloc(value, children = []) {
        const id = this.nextId++;
        this.objects.set(id, {
            header: {
                id,
                strong: 1,
                weak: 0,
                color: "black",
                buffered: false,
                children: [...children],
            },
            value,
        });
        this.allocated += 1;
        return id;
    }
    retain(id) {
        const obj = this.objects.get(id);
        if (!obj) {
            return;
        }
        obj.header.strong += 1;
        obj.header.color = "black";
    }
    release(id) {
        const obj = this.objects.get(id);
        if (!obj) {
            return;
        }
        obj.header.strong -= 1;
        if (obj.header.strong === 0) {
            this.free(id);
            return;
        }
        // Possible cycle participant
        if (obj.header.color !== "purple") {
            obj.header.color = "purple";
            if (!obj.header.buffered) {
                obj.header.buffered = true;
                this.cycleBuffer.push(id);
            }
        }
        this.collectIncremental();
    }
    weakRetain(id) {
        const obj = this.objects.get(id);
        if (obj) {
            obj.header.weak += 1;
        }
    }
    weakRelease(id) {
        const obj = this.objects.get(id);
        if (!obj) {
            return;
        }
        obj.header.weak -= 1;
        if (obj.header.strong === 0 && obj.header.weak === 0) {
            this.objects.delete(id);
        }
    }
    deref(id) {
        const obj = this.objects.get(id);
        if (!obj) {
            return (0, values_1.newError)(`dangling pointer to object #${id}`);
        }
        return obj.value;
    }
    setChildren(id, children) {
        const obj = this.objects.get(id);
        if (obj) {
            obj.header.children = [...children];
        }
    }
    updateValue(id, value) {
        const obj = this.objects.get(id);
        if (obj) {
            obj.value = value;
        }
    }
    rc(value, children = []) {
        const id = this.alloc(value, children);
        // alloc starts at strong=1; Rc constructor retains → bump then drop one
        const ptr = new Rc(this, id);
        this.release(id); // balance the initial alloc retain vs Rc retain
        return ptr;
    }
    arc(value, children = []) {
        const id = this.alloc(value, children);
        const ptr = new Arc(this, id);
        this.release(id);
        return ptr;
    }
    /** Run a bounded amount of cycle collection work. */
    collectIncremental(budget = this.collectBudget) {
        let work = 0;
        while (work < budget && this.cycleBuffer.length > 0) {
            const id = this.cycleBuffer.shift();
            const obj = this.objects.get(id);
            if (!obj) {
                continue;
            }
            obj.header.buffered = false;
            if (obj.header.color === "purple") {
                this.markGray(id);
                this.scan(id);
                this.collectWhite(id);
                work += 1;
            }
        }
        return work;
    }
    /** Force a full cycle collection pass. */
    collectCycles() {
        let total = 0;
        let n = 0;
        do {
            n = this.collectIncremental(10_000);
            total += n;
        } while (n > 0);
        return total;
    }
    free(id) {
        const obj = this.objects.get(id);
        if (!obj) {
            return;
        }
        for (const child of obj.header.children) {
            this.release(child);
        }
        if (obj.header.weak === 0) {
            this.objects.delete(id);
        }
        else {
            // Keep shell for weak refs; mark dead by clearing children
            obj.header.children = [];
            obj.header.color = "black";
        }
        this.freed += 1;
    }
    markGray(id) {
        const obj = this.objects.get(id);
        if (!obj || obj.header.color === "gray") {
            return;
        }
        obj.header.color = "gray";
        for (const child of obj.header.children) {
            const c = this.objects.get(child);
            if (!c) {
                continue;
            }
            c.header.strong -= 1;
            this.markGray(child);
        }
    }
    scan(id) {
        const obj = this.objects.get(id);
        if (!obj || obj.header.color !== "gray") {
            return;
        }
        if (obj.header.strong > 0) {
            this.scanBlack(id);
            return;
        }
        obj.header.color = "white";
        for (const child of obj.header.children) {
            this.scan(child);
        }
    }
    scanBlack(id) {
        const obj = this.objects.get(id);
        if (!obj) {
            return;
        }
        obj.header.color = "black";
        for (const child of obj.header.children) {
            const c = this.objects.get(child);
            if (!c) {
                continue;
            }
            c.header.strong += 1;
            if (c.header.color !== "black") {
                this.scanBlack(child);
            }
        }
    }
    collectWhite(id) {
        const obj = this.objects.get(id);
        if (!obj || obj.header.color !== "white") {
            return;
        }
        obj.header.color = "black";
        for (const child of [...obj.header.children]) {
            this.collectWhite(child);
        }
        this.objects.delete(id);
        this.freed += 1;
    }
}
exports.MemoryEngine = MemoryEngine;
/** Process-wide default engine used by the evaluator/runtime. */
let defaultEngine = null;
function getMemoryEngine() {
    if (!defaultEngine) {
        defaultEngine = new MemoryEngine();
    }
    return defaultEngine;
}
function resetMemoryEngine() {
    defaultEngine = new MemoryEngine();
    return defaultEngine;
}
//# sourceMappingURL=memory.js.map