export * from './system.mjs';
import { createReactiveSystem } from './system.mjs';
const pauseStack = [];
const queuedEffects = [];
const { link, unlink, propagate, checkDirty, endTracking, startTracking, shallowPropagate, } = createReactiveSystem({
    update(signal) {
        if ('getter' in signal) {
            return updateComputed(signal);
        }
        else {
            return updateSignal(signal, signal.value);
        }
    },
    notify,
    unwatched(node) {
        if ('getter' in node) {
            let toRemove = node.deps;
            if (toRemove !== undefined) {
                node.flags = 17;
                do {
                    toRemove = unlink(toRemove, node);
                } while (toRemove !== undefined);
            }
        }
        else if (!('previousValue' in node)) {
            effectOper.call(node);
        }
    },
});
export let batchDepth = 0;
let notifyIndex = 0;
let queuedEffectsLength = 0;
let activeSub;
let activeScope;
export function getCurrentSub() {
    return activeSub;
}
export function setCurrentSub(sub) {
    const prevSub = activeSub;
    activeSub = sub;
    return prevSub;
}
export function getCurrentScope() {
    return activeScope;
}
export function setCurrentScope(scope) {
    const prevScope = activeScope;
    activeScope = scope;
    return prevScope;
}
export function startBatch() {
    ++batchDepth;
}
export function endBatch() {
    if (!--batchDepth) {
        flush();
    }
}
export function pauseTracking() {
    pauseStack.push(setCurrentSub(undefined));
}
export function resumeTracking() {
    setCurrentSub(pauseStack.pop());
}
export function signal(initialValue) {
    return signalOper.bind({
        previousValue: initialValue,
        value: initialValue,
        subs: undefined,
        subsTail: undefined,
        flags: 1,
    });
}
export function computed(getter) {
    return computedOper.bind({
        value: undefined,
        subs: undefined,
        subsTail: undefined,
        deps: undefined,
        depsTail: undefined,
        flags: 17,
        getter: getter,
    });
}
export function effect(fn) {
    const e = {
        fn,
        subs: undefined,
        subsTail: undefined,
        deps: undefined,
        depsTail: undefined,
        flags: 2,
    };
    if (activeSub !== undefined) {
        link(e, activeSub);
    }
    else if (activeScope !== undefined) {
        link(e, activeScope);
    }
    const prev = setCurrentSub(e);
    try {
        e.fn();
    }
    finally {
        setCurrentSub(prev);
    }
    return effectOper.bind(e);
}
export function effectScope(fn) {
    const e = {
        deps: undefined,
        depsTail: undefined,
        subs: undefined,
        subsTail: undefined,
        flags: 0,
    };
    if (activeScope !== undefined) {
        link(e, activeScope);
    }
    const prevSub = setCurrentSub(undefined);
    const prevScope = setCurrentScope(e);
    try {
        fn();
    }
    finally {
        setCurrentScope(prevScope);
        setCurrentSub(prevSub);
    }
    return effectOper.bind(e);
}
function updateComputed(c) {
    const prevSub = setCurrentSub(c);
    startTracking(c);
    try {
        const oldValue = c.value;
        return oldValue !== (c.value = c.getter(oldValue));
    }
    finally {
        setCurrentSub(prevSub);
        endTracking(c);
    }
}
function updateSignal(s, value) {
    s.flags = 1;
    return s.previousValue !== (s.previousValue = value);
}
function notify(e) {
    const flags = e.flags;
    if (!(flags & 64)) {
        e.flags = flags | 64;
        const subs = e.subs;
        if (subs !== undefined) {
            notify(subs.sub);
        }
        else {
            queuedEffects[queuedEffectsLength++] = e;
        }
    }
}
function run(e, flags) {
    if (flags & 16
        || (flags & 32 && checkDirty(e.deps, e))) {
        const prev = setCurrentSub(e);
        startTracking(e);
        try {
            e.fn();
        }
        finally {
            setCurrentSub(prev);
            endTracking(e);
        }
        return;
    }
    else if (flags & 32) {
        e.flags = flags & ~32;
    }
    let link = e.deps;
    while (link !== undefined) {
        const dep = link.dep;
        const depFlags = dep.flags;
        if (depFlags & 64) {
            run(dep, dep.flags = depFlags & ~64);
        }
        link = link.nextDep;
    }
}
function flush() {
    while (notifyIndex < queuedEffectsLength) {
        const effect = queuedEffects[notifyIndex];
        queuedEffects[notifyIndex++] = undefined;
        run(effect, effect.flags &= ~64);
    }
    notifyIndex = 0;
    queuedEffectsLength = 0;
}
function computedOper() {
    const flags = this.flags;
    if (flags & 16
        || (flags & 32 && checkDirty(this.deps, this))) {
        if (updateComputed(this)) {
            const subs = this.subs;
            if (subs !== undefined) {
                shallowPropagate(subs);
            }
        }
    }
    else if (flags & 32) {
        this.flags = flags & ~32;
    }
    if (activeSub !== undefined) {
        link(this, activeSub);
    }
    else if (activeScope !== undefined) {
        link(this, activeScope);
    }
    return this.value;
}
function signalOper(...value) {
    if (value.length) {
        if (this.value !== (this.value = value[0])) {
            this.flags = 17;
            const subs = this.subs;
            if (subs !== undefined) {
                propagate(subs);
                if (!batchDepth) {
                    flush();
                }
            }
        }
    }
    else {
        const value = this.value;
        if (this.flags & 16) {
            if (updateSignal(this, value)) {
                const subs = this.subs;
                if (subs !== undefined) {
                    shallowPropagate(subs);
                }
            }
        }
        if (activeSub !== undefined) {
            link(this, activeSub);
        }
        return value;
    }
}
function effectOper() {
    let dep = this.deps;
    while (dep !== undefined) {
        dep = unlink(dep, this);
    }
    const sub = this.subs;
    if (sub !== undefined) {
        unlink(sub);
    }
    this.flags = 0;
}
