import { createReactiveSystem } from './system.mjs';
let cycle = 0;
const queuedEffects = [];
const { link, unlink, propagate, checkDirty, 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 ('fn' in node) {
            effectOper.call(node);
        }
        else if (!('previousValue' in node)) {
            effectScopeOper.call(node);
        }
    },
});
let batchDepth = 0;
let notifyIndex = 0;
let queuedEffectsLength = 0;
let activeSub;
export function getActiveSub() {
    return activeSub;
}
export function setActiveSub(sub) {
    const prevSub = activeSub;
    activeSub = sub;
    return prevSub;
}
export function getBatchDepth() {
    return batchDepth;
}
export function startBatch() {
    ++batchDepth;
}
export function endBatch() {
    if (!--batchDepth) {
        flush();
    }
}
export function isSignal(fn) {
    return fn.name === 'bound signalOper';
}
export function isComputed(fn) {
    return fn.name === 'bound computedOper';
}
export function isEffect(fn) {
    return fn.name === 'bound effectOper';
}
export function isEffectScope(fn) {
    return fn.name === 'bound effectScopeOper';
}
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: 0,
        getter: getter,
    });
}
export function effect(fn) {
    const e = {
        fn,
        subs: undefined,
        subsTail: undefined,
        deps: undefined,
        depsTail: undefined,
        flags: 2,
    };
    const prevSub = setActiveSub(e);
    if (prevSub !== undefined) {
        link(e, prevSub, 0);
    }
    try {
        e.fn();
    }
    finally {
        activeSub = prevSub;
    }
    return effectOper.bind(e);
}
export function effectScope(fn) {
    const e = {
        deps: undefined,
        depsTail: undefined,
        subs: undefined,
        subsTail: undefined,
        flags: 0,
    };
    const prevSub = setActiveSub(e);
    if (prevSub !== undefined) {
        link(e, prevSub, 0);
    }
    try {
        fn();
    }
    finally {
        activeSub = prevSub;
    }
    return effectScopeOper.bind(e);
}
function updateComputed(c) {
    ++cycle;
    c.depsTail = undefined;
    c.flags = 5;
    const prevSub = setActiveSub(c);
    try {
        const oldValue = c.value;
        return oldValue !== (c.value = c.getter(oldValue));
    }
    finally {
        activeSub = prevSub;
        c.flags &= ~4;
        purgeDeps(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)
                || (e.flags = flags & ~32, false)))) {
        ++cycle;
        e.depsTail = undefined;
        e.flags = 6;
        const prevSub = setActiveSub(e);
        try {
            e.fn();
        }
        finally {
            activeSub = prevSub;
            e.flags &= ~4;
            purgeDeps(e);
        }
    }
    else {
        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)
                || (this.flags = flags & ~32, false)))) {
        if (updateComputed(this)) {
            const subs = this.subs;
            if (subs !== undefined) {
                shallowPropagate(subs);
            }
        }
    }
    else if (!flags) {
        this.flags = 1;
        const prevSub = setActiveSub(this);
        try {
            this.value = this.getter();
        }
        finally {
            activeSub = prevSub;
        }
    }
    const sub = activeSub;
    if (sub !== undefined) {
        link(this, sub, cycle);
    }
    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);
                }
            }
        }
        let sub = activeSub;
        while (sub !== undefined) {
            if (sub.flags & 3) {
                link(this, sub, cycle);
                break;
            }
            sub = sub.subs?.sub;
        }
        return value;
    }
}
function effectOper() {
    effectScopeOper.call(this);
    this.flags = 0;
}
function effectScopeOper() {
    let dep = this.deps;
    while (dep !== undefined) {
        dep = unlink(dep, this);
    }
    const sub = this.subs;
    if (sub !== undefined) {
        unlink(sub);
    }
}
function purgeDeps(sub) {
    const depsTail = sub.depsTail;
    let toRemove = depsTail !== undefined ? depsTail.nextDep : sub.deps;
    while (toRemove !== undefined) {
        toRemove = unlink(toRemove, sub);
    }
}
