import { printTree } from 'tree-dump/lib/printTree';
import { get } from '@jsonjoy.com/json-pointer/lib/get';
import { toPath } from '@jsonjoy.com/json-pointer/lib/util';
import { find } from './find';
import { Timestamp } from '../../../json-crdt-patch/clock';
import { ObjNode, ArrNode, BinNode, ConNode, VecNode, ValNode, StrNode, RootNode } from '../../nodes';
import { NodeEvents } from './NodeEvents';
import { FanOut } from 'thingies/lib/fanout';
import { PatchBuilder } from '../../../json-crdt-patch/PatchBuilder';
import { MergeFanOut, MicrotaskBufferFanOut } from './fanout';
import { ExtNode } from '../../extensions/ExtNode';
import * as diff from '../../../json-crdt-diff';
import { proxy$ } from './proxy';
const breakPath = (path) => {
    if (!path)
        return [void 0, ''];
    if (typeof path === 'number')
        return [void 0, path];
    if (typeof path === 'string')
        path = toPath(path);
    switch (path.length) {
        case 0:
            return [void 0, ''];
        case 1:
            return [void 0, path[0]];
        default: {
            const key = path[path.length - 1];
            const parent = path.slice(0, -1);
            return [parent, key];
        }
    }
};
/**
 * A generic local changes API for a JSON CRDT node.
 *
 * @category Local API
 */
export class NodeApi {
    node;
    api;
    constructor(node, api) {
        this.node = node;
        this.api = api;
    }
    /** @ignore */
    ev = undefined;
    /**
     * Event target for listening to node changes. You can subscribe to `"view"`
     * events, which are triggered every time the node's view changes.
     *
     * ```ts
     * node.events.on('view', () => {
     *   // do something...
     * });
     * ```
     */
    get events() {
        const et = this.ev;
        return et || (this.ev = new NodeEvents(this));
    }
    /**
     * Find a child node at the given path starting from this node.
     *
     * @param path Path to the child node to find.
     * @returns JSON CRDT node at the given path.
     */
    find(path) {
        let node = this.node;
        /**
         * @todo Remove this .child() loop, and remove the `.child()` method from JsonNode interface.
         */
        if (path === undefined) {
            if (typeof node.child === 'function') {
                const child = node.child();
                if (!child) {
                    if (node instanceof RootNode)
                        return node;
                    throw new Error('NO_CHILD');
                }
                return child;
            }
            throw new Error('CANNOT_IN');
        }
        if (typeof path === 'string' && !!path && path[0] !== '/')
            path = '/' + path;
        if (typeof path === 'number')
            path = [path];
        while (node instanceof ValNode)
            node = node.child();
        return find(node, path);
    }
    /**
     * Find a child node at the given path starting from this node and wrap it in
     * a local changes API.
     *
     * @param path Path to the child node to find.
     * @returns Local changes API for the child node at the given path.
     */
    in(path) {
        const node = this.find(path);
        return this.api.wrap(node);
    }
    asVal() {
        if (this.node instanceof ValNode)
            return this.api.wrap(this.node);
        throw new Error('NOT_VAL');
    }
    asStr() {
        if (this.node instanceof StrNode)
            return this.api.wrap(this.node);
        throw new Error('NOT_STR');
    }
    asBin() {
        if (this.node instanceof BinNode)
            return this.api.wrap(this.node);
        throw new Error('NOT_BIN');
    }
    asArr() {
        if (this.node instanceof ArrNode)
            return this.api.wrap(this.node);
        throw new Error('NOT_ARR');
    }
    asVec() {
        if (this.node instanceof VecNode)
            return this.api.wrap(this.node);
        throw new Error('NOT_VEC');
    }
    asObj() {
        if (this.node instanceof ObjNode)
            return this.api.wrap(this.node);
        throw new Error('NOT_OBJ');
    }
    asCon() {
        if (this.node instanceof ConNode)
            return this.api.wrap(this.node);
        throw new Error('NOT_CON');
    }
    asExt(ext) {
        let extNode;
        const node = this.node;
        if (node instanceof ExtNode)
            extNode = node;
        if (node instanceof VecNode)
            extNode = node.ext();
        if (!extNode)
            throw new Error('NOT_EXT');
        const api = this.api.wrap(extNode);
        if (!ext)
            return api;
        if (api instanceof ext.Api)
            return api;
        throw new Error('NOT_EXT');
    }
    val(path) {
        return this.in(path).asVal();
    }
    str(path) {
        return this.in(path).asStr();
    }
    bin(path) {
        return this.in(path).asBin();
    }
    arr(path) {
        return this.in(path).asArr();
    }
    vec(path) {
        return this.in(path).asVec();
    }
    obj(path) {
        return this.in(path).asObj();
    }
    con(path) {
        return this.in(path).asCon();
    }
    view() {
        return this.node.view();
    }
    select(path, leaf) {
        try {
            let node = path !== void 0 ? this.find(path) : this.node;
            if (leaf)
                while (node instanceof ValNode)
                    node = node.child();
            return this.api.wrap(node);
        }
        catch (_e) {
            return;
        }
    }
    read(path) {
        const view = this.view();
        if (Array.isArray(path))
            return get(view, path);
        if (!path)
            return view;
        let path2 = path + '';
        if (path && path2[0] !== '/')
            path2 = '/' + path2;
        return get(view, toPath(path2));
    }
    add(path, value) {
        const [parent, key] = breakPath(path);
        ADD: try {
            const node = this.select(parent, true);
            if (node instanceof ObjApi) {
                node.set({ [key]: value });
            }
            else if (node instanceof ArrApi || node instanceof StrApi || node instanceof BinApi) {
                const length = node.length();
                let index = 0;
                if (typeof key === 'number')
                    index = key;
                else if (key === '-')
                    index = length;
                else {
                    index = ~~key;
                    if (index + '' !== key)
                        break ADD;
                }
                if (index !== index)
                    break ADD;
                if (index < 0)
                    index = 0;
                if (index > length)
                    index = length;
                if (node instanceof ArrApi)
                    node.ins(index, Array.isArray(value) ? value : [value]);
                else if (node instanceof StrApi)
                    node.ins(index, value + '');
                else if (node instanceof BinApi) {
                    if (!(value instanceof Uint8Array))
                        break ADD;
                    node.ins(index, value);
                }
            }
            else if (node instanceof VecApi) {
                node.set([[~~key, value]]);
            }
            else
                break ADD;
            return true;
        }
        catch { }
        return false;
    }
    replace(path, value) {
        const [parent, key] = breakPath(path);
        REPLACE: try {
            const node = this.select(parent, true);
            if (node instanceof ObjApi) {
                const keyStr = key + '';
                if (!node.has(keyStr))
                    break REPLACE;
                node.set({ [key]: value });
            }
            else if (node instanceof ArrApi) {
                const length = node.length();
                let index = 0;
                if (typeof key === 'number')
                    index = key;
                else {
                    index = ~~key;
                    if (index + '' !== key)
                        break REPLACE;
                }
                if (index !== index || index < 0 || index > length)
                    break REPLACE;
                if (index === length)
                    node.ins(index, [value]);
                else
                    node.upd(index, value);
            }
            else if (node instanceof VecApi)
                node.set([[~~key, value]]);
            else
                break REPLACE;
            return true;
        }
        catch { }
        return false;
    }
    remove(path, length = 1) {
        const [parent, key] = breakPath(path);
        REMOVE: try {
            const node = this.select(parent, true);
            if (node instanceof ObjApi) {
                const keyStr = key + '';
                if (!node.has(keyStr))
                    break REMOVE;
                node.del([keyStr]);
            }
            else if (node instanceof ArrApi || node instanceof StrApi || node instanceof BinApi) {
                const len = node.length();
                let index = 0;
                if (typeof key === 'number')
                    index = key;
                else if (key === '-')
                    index = length;
                else {
                    index = ~~key;
                    if (index + '' !== key)
                        break REMOVE;
                }
                if (index !== index || index < 0 || index > len)
                    break REMOVE;
                node.del(index, Math.min(length, len - index));
            }
            else if (node instanceof VecApi) {
                node.set([[~~key, void 0]]);
            }
            else
                break REMOVE;
            return true;
        }
        catch { }
        return false;
    }
    diff(value) {
        return diff.diff(this, value);
    }
    merge(value) {
        return diff.merge(this, value);
    }
    op(operation) {
        if (!Array.isArray(operation))
            return false;
        const [type, path, value] = operation;
        switch (type) {
            case 'add':
                return this.add(path, value);
            case 'replace':
                return this.replace(path, value);
            case 'merge':
                return !!this.select(path)?.merge(value);
            case 'remove':
                return this.remove(path, value);
        }
    }
    get s() {
        return { $: this };
    }
    get $() {
        return proxy$((path) => {
            try {
                return this.api.wrap(this.find(path));
            }
            catch {
                return;
            }
        }, '$');
    }
    toString(tab = '') {
        const name = this.constructor === NodeApi ? '*' : this.node.name();
        return 'api(' + name + ')' + printTree(tab, [(tab) => this.node.toString(tab)]);
    }
}
/**
 * Represents the local changes API for the `con` JSON CRDT node {@link ConNode}.
 *
 * @category Local API
 */
export class ConApi extends NodeApi {
    /**
     * Returns a proxy object for this node.
     */
    get s() {
        return { $: this };
    }
}
/**
 * Local changes API for the `val` JSON CRDT node {@link ValNode}.
 *
 * @category Local API
 */
export class ValApi extends NodeApi {
    /**
     * Get API instance of the inner node.
     * @returns Inner node API.
     */
    get() {
        return this.in();
    }
    /**
     * Sets the value of the node.
     *
     * @param json JSON/CBOR value or ID (logical timestamp) of the value to set.
     * @returns Reference to itself.
     */
    set(json) {
        const { api, node } = this;
        const builder = api.builder;
        const val = builder.constOrJson(json);
        api.builder.setVal(node.id, val);
        api.apply();
    }
    /**
     * Returns a proxy object for this node. Allows to access the value of the
     * node by accessing the `.val` property.
     */
    get s() {
        const self = this;
        const proxy = {
            $: this,
            get _() {
                const childNode = self.node.node();
                return self.api.wrap(childNode).s;
            },
        };
        return proxy;
    }
}
/**
 * Local changes API for the `vec` JSON CRDT node {@link VecNode}.
 *
 * @category Local API
 */
export class VecApi extends NodeApi {
    /**
     * Get API instance of a child node.
     *
     * @param key Object key to get.
     * @returns A specified child node API.
     */
    get(key) {
        return this.in(key);
    }
    /**
     * Sets a list of elements to the given values.
     *
     * @param entries List of index-value pairs to set.
     * @returns Reference to itself.
     */
    set(entries) {
        const { api, node } = this;
        const { builder } = api;
        builder.insVec(node.id, entries.map(([index, json]) => [index, builder.constOrJson(json)]));
        api.apply();
    }
    push(...values) {
        const length = this.length();
        this.set(values.map((value, index) => [length + index, value]));
    }
    /**
     * Get the length of the vector without materializing it to a view.
     *
     * @returns Length of the vector.
     */
    length() {
        return this.node.elements.length;
    }
    /**
     * Returns a proxy object for this node. Allows to access vector elements by
     * index.
     */
    get s() {
        const proxy = new Proxy({}, {
            get: (target, prop, receiver) => {
                if (prop === '$')
                    return this;
                if (prop === 'toExt')
                    return () => this.asExt();
                const index = Number(prop);
                if (Number.isNaN(index))
                    throw new Error('INVALID_INDEX');
                const child = this.node.get(index);
                if (!child)
                    throw new Error('OUT_OF_BOUNDS');
                return this.api.wrap(child).s;
            },
        });
        return proxy;
    }
}
/**
 * Local changes API for the `obj` JSON CRDT node {@link ObjNode}.
 *
 * @category Local API
 */
export class ObjApi extends NodeApi {
    /**
     * Get API instance of a child node.
     *
     * @param key Object key to get.
     * @returns A specified child node API.
     */
    get(key) {
        return this.in(key);
    }
    /**
     * Sets a list of keys to the given values.
     *
     * @param entries List of key-value pairs to set.
     * @returns Reference to itself.
     */
    set(entries) {
        const { api, node } = this;
        const { builder } = api;
        builder.insObj(node.id, Object.entries(entries).map(([key, json]) => [key, builder.constOrJson(json)]));
        api.apply();
    }
    /**
     * Deletes a list of keys from the object.
     *
     * @param keys List of keys to delete.
     * @returns Reference to itself.
     */
    del(keys) {
        const { api, node } = this;
        const { builder } = api;
        api.builder.insObj(node.id, keys.map((key) => [key, builder.con(undefined)]));
        api.apply();
    }
    /**
     * Checks if a key exists in the object.
     *
     * @param key Key to check.
     * @returns True if the key exists, false otherwise.
     */
    has(key) {
        return this.node.keys.has(key);
    }
    /**
     * Returns a proxy object for this node. Allows to access object properties
     * by key.
     */
    get s() {
        const proxy = new Proxy({}, {
            get: (target, prop, receiver) => {
                if (prop === '$')
                    return this;
                const key = String(prop);
                const child = this.node.get(key);
                if (!child)
                    throw new Error('NO_SUCH_KEY');
                return this.api.wrap(child).s;
            },
        });
        return proxy;
    }
}
/**
 * Local changes API for the `str` JSON CRDT node {@link StrNode}. This API
 * allows to insert and delete bytes in the UTF-16 string by referencing its
 * local character positions.
 *
 * @category Local API
 */
export class StrApi extends NodeApi {
    /**
     * Inserts text at a given position.
     *
     * @param index Position at which to insert text.
     * @param text Text to insert.
     * @returns Reference to itself.
     */
    ins(index, text) {
        const { api, node } = this;
        api.onBeforeLocalChange.emit(api.next);
        const builder = api.builder;
        builder.pad();
        const nextTime = api.builder.nextTime();
        const id = new Timestamp(builder.clock.sid, nextTime);
        const after = node.insAt(index, id, text);
        if (!after)
            throw new Error('OUT_OF_BOUNDS');
        builder.insStr(node.id, after, text);
        api.advance();
    }
    /**
     * Deletes a range of text at a given position.
     *
     * @param index Position at which to delete text.
     * @param length Number of UTF-16 code units to delete.
     * @returns Reference to itself.
     */
    del(index, length) {
        const { api, node } = this;
        api.onBeforeLocalChange.emit(api.next);
        const builder = api.builder;
        builder.pad();
        const spans = node.findInterval(index, length);
        if (!spans)
            throw new Error('OUT_OF_BOUNDS');
        node.delete(spans);
        builder.del(node.id, spans);
        api.advance();
    }
    /**
     * Given a character index in local coordinates, find the ID of the character
     * in the global coordinates.
     *
     * @param index Index of the character or `-1` for before the first character.
     * @returns ID of the character after which the given position is located.
     */
    findId(index) {
        const node = this.node;
        const length = node.length();
        const max = length - 1;
        if (index > max)
            index = max;
        if (index < 0)
            return node.id;
        const id = node.find(index);
        return id || node.id;
    }
    /**
     * Given a position in global coordinates, find the position in local
     * coordinates.
     *
     * @param id ID of the character.
     * @returns Index of the character in local coordinates. Returns -1 if the
     *          the position refers to the beginning of the string.
     */
    findPos(id) {
        const node = this.node;
        const nodeId = node.id;
        if (nodeId.sid === id.sid && nodeId.time === id.time)
            return -1;
        const chunk = node.findById(id);
        if (!chunk)
            return -1;
        const pos = node.pos(chunk);
        return pos + (chunk.del ? 0 : id.time - chunk.id.time);
    }
    /**
     * Get the length of the string without materializing it to a view.
     *
     * @returns Length of the string.
     */
    length() {
        return this.node.length();
    }
    /**
     * Returns a proxy object for this node.
     */
    get s() {
        return { $: this };
    }
}
/**
 * Local changes API for the `bin` JSON CRDT node {@link BinNode}. This API
 * allows to insert and delete bytes in the binary string by referencing their
 * local index.
 *
 * @category Local API
 */
export class BinApi extends NodeApi {
    /**
     * Inserts octets at a given position.
     *
     * @param index Position at which to insert octets.
     * @param data Octets to insert.
     * @returns Reference to itself.
     */
    ins(index, data) {
        const { api, node } = this;
        const after = !index ? node.id : node.find(index - 1);
        if (!after)
            throw new Error('OUT_OF_BOUNDS');
        api.builder.insBin(node.id, after, data);
        api.apply();
    }
    /**
     * Deletes a range of octets at a given position.
     *
     * @param index Position at which to delete octets.
     * @param length Number of octets to delete.
     * @returns Reference to itself.
     */
    del(index, length) {
        const { api, node } = this;
        const spans = node.findInterval(index, length);
        if (!spans)
            throw new Error('OUT_OF_BOUNDS');
        api.builder.del(node.id, spans);
        api.apply();
    }
    /**
     * Get the length of the binary blob without materializing it to a view.
     *
     * @returns Length of the binary blob.
     */
    length() {
        return this.node.length();
    }
    /**
     * Returns a proxy object for this node.
     */
    get s() {
        return { $: this };
    }
}
/**
 * Local changes API for the `arr` JSON CRDT node {@link ArrNode}. This API
 * allows to insert and delete elements in the array by referencing their local
 * index.
 *
 * @category Local API
 */
export class ArrApi extends NodeApi {
    /**
     * Get API instance of a child node.
     *
     * @param index Index of the element to get.
     * @returns Child node API for the element at the given index.
     */
    get(index) {
        return this.in(index);
    }
    /**
     * Inserts elements at a given position.
     *
     * @param index Position at which to insert elements.
     * @param values Values or schema of the elements to insert.
     */
    ins(index, values) {
        const { api, node } = this;
        const { builder } = api;
        const after = !index ? node.id : node.find(index - 1);
        if (!after)
            throw new Error('OUT_OF_BOUNDS');
        const valueIds = [];
        for (let i = 0; i < values.length; i++)
            valueIds.push(builder.json(values[i]));
        builder.insArr(node.id, after, valueIds);
        api.apply();
    }
    /**
     * Inserts elements at the end of the array.
     *
     * @param values Values or schema of the elements to insert at the end of the array.
     */
    push(...values) {
        const length = this.length();
        this.ins(length, values);
    }
    /**
     * Updates (overwrites) an element at a given position.
     *
     * @param index Position at which to update the element.
     * @param value Value or schema of the element to replace with.
     */
    upd(index, value) {
        const { api, node } = this;
        const ref = node.getId(index);
        if (!ref)
            throw new Error('OUT_OF_BOUNDS');
        const { builder } = api;
        builder.updArr(node.id, ref, builder.constOrJson(value));
        api.apply();
    }
    /**
     * Deletes a range of elements at a given position.
     *
     * @param index Position at which to delete elements.
     * @param length Number of elements to delete.
     * @returns Reference to itself.
     */
    del(index, length) {
        const { api, node } = this;
        const spans = node.findInterval(index, length);
        if (!spans)
            throw new Error('OUT_OF_BOUNDS');
        api.builder.del(node.id, spans);
        api.apply();
    }
    /**
     * Get the length of the array without materializing it to a view.
     *
     * @returns Length of the array.
     */
    length() {
        return this.node.length();
    }
    /**
     * Returns a proxy object that allows to access array elements by index.
     *
     * @returns Proxy object that allows to access array elements by index.
     */
    get s() {
        const proxy = new Proxy({}, {
            get: (target, prop, receiver) => {
                if (prop === '$')
                    return this;
                const index = Number(prop);
                if (Number.isNaN(index))
                    throw new Error('INVALID_INDEX');
                const child = this.node.getNode(index);
                if (!child)
                    throw new Error('OUT_OF_BOUNDS');
                return this.api.wrap(child).s;
            },
        });
        return proxy;
    }
}
/**
 * Local changes API for a JSON CRDT model. This class is the main entry point
 * for executing local user actions on a JSON CRDT document.
 *
 * @category Local API
 */
export class ModelApi extends ValApi {
    model;
    /**
     * Patch builder for the local changes.
     */
    builder;
    /**
     * Index of the next operation in builder's patch to be committed locally.
     *
     * @ignore
     */
    next = 0;
    /** Emitted before the model is reset, using the `.reset()` method. */
    onBeforeReset = new FanOut();
    /** Emitted after the model is reset, using the `.reset()` method. */
    onReset = new FanOut();
    /** Emitted before a patch is applied using `model.applyPatch()`. */
    onBeforePatch = new FanOut();
    /** Emitted after a patch is applied using `model.applyPatch()`. */
    onPatch = new FanOut();
    /** Emitted before local changes through `model.api` are applied. */
    onBeforeLocalChange = new FanOut();
    /** Emitted after local changes through `model.api` are applied. */
    onLocalChange = new FanOut();
    /**
     * Emitted after local changes through `model.api` are applied. Same as
     * `.onLocalChange`, but this event buffered withing a microtask.
     */
    onLocalChanges = new MicrotaskBufferFanOut(this.onLocalChange);
    /** Emitted before a transaction is started. */
    onBeforeTransaction = new FanOut();
    /** Emitted after transaction completes. */
    onTransaction = new FanOut();
    /** Emitted when the model changes. Combines `onReset`, `onPatch` and `onLocalChange`. */
    onChange = new MergeFanOut([
        this.onReset,
        this.onPatch,
        this.onLocalChange,
    ]);
    /** Emitted when the model changes. Same as `.onChange`, but this event is emitted once per microtask. */
    onChanges = new MicrotaskBufferFanOut(this.onChange);
    /** Emitted when the `model.api` builder change buffer is flushed. */
    onFlush = new FanOut();
    /**
     * @param model Model instance on which the API operates.
     */
    constructor(model) {
        super(model.root, void 0);
        this.model = model;
        this.api = this;
        this.builder = new PatchBuilder(model.clock);
        model.onbeforereset = () => this.onBeforeReset.emit();
        model.onreset = () => this.onReset.emit();
        model.onbeforepatch = (patch) => this.onBeforePatch.emit(patch);
        model.onpatch = (patch) => this.onPatch.emit(patch);
    }
    wrap(node) {
        if (node instanceof ValNode)
            return node.api || (node.api = new ValApi(node, this));
        else if (node instanceof StrNode)
            return node.api || (node.api = new StrApi(node, this));
        else if (node instanceof BinNode)
            return node.api || (node.api = new BinApi(node, this));
        else if (node instanceof ArrNode)
            return node.api || (node.api = new ArrApi(node, this));
        else if (node instanceof ObjNode)
            return node.api || (node.api = new ObjApi(node, this));
        else if (node instanceof ConNode)
            return node.api || (node.api = new ConApi(node, this));
        else if (node instanceof VecNode)
            return node.api || (node.api = new VecApi(node, this));
        else if (node instanceof ExtNode) {
            if (node.api)
                return node.api;
            const extension = this.model.ext.get(node.extId);
            return (node.api = new extension.Api(node, this));
        }
        else
            throw new Error('UNKNOWN_NODE');
    }
    /**
     * Given a JSON/CBOR value, constructs CRDT nodes recursively out of it and
     * sets the root node of the model to the constructed nodes.
     *
     * @param json JSON/CBOR value to set as the view of the model.
     * @returns Reference to itself.
     *
     * @deprecated Use `.set()` instead.
     */
    root(json) {
        return this.set(json);
    }
    set(json) {
        super.set(json);
        return this;
    }
    /**
     * Apply locally any operations from the `.builder`, which haven't been
     * applied yet.
     */
    apply() {
        const ops = this.builder.patch.ops;
        const length = ops.length;
        const model = this.model;
        const from = this.next;
        this.onBeforeLocalChange.emit(from);
        for (let i = this.next; i < length; i++)
            model.applyOperation(ops[i]);
        this.next = length;
        model.tick++;
        this.onLocalChange.emit(from);
    }
    /**
     * Advance patch pointer to the end without applying the operations. With the
     * idea that they have already been applied locally.
     *
     * You need to manually call `this.onBeforeLocalChange.emit(this.next)` before
     * calling this method.
     *
     * @ignore
     */
    advance() {
        const from = this.next;
        this.next = this.builder.patch.ops.length;
        this.model.tick++;
        this.onLocalChange.emit(from);
    }
    inTx = false;
    transaction(callback) {
        if (this.inTx)
            callback();
        else {
            this.inTx = true;
            try {
                this.onBeforeTransaction.emit();
                callback();
                this.onTransaction.emit();
            }
            finally {
                this.inTx = false;
            }
        }
    }
    /**
     * Flushes the builder and returns a patch.
     *
     * @returns A JSON CRDT patch.
     * @todo Make this return undefined if there are no operations in the builder.
     */
    flush() {
        const patch = this.builder.flush();
        this.next = 0;
        if (patch.ops.length)
            this.onFlush.emit(patch);
        return patch;
    }
    stopAutoFlush = undefined;
    /**
     * Begins to automatically flush buffered operations into patches, grouping
     * operations by microtasks or by transactions. To capture the patch, listen
     * to the `.onFlush` event.
     *
     * @returns Callback to stop auto flushing.
     */
    autoFlush(drainNow = false) {
        const drain = () => this.builder.patch.ops.length && this.flush();
        const onLocalChangesUnsubscribe = this.onLocalChanges.listen(drain);
        const onBeforeTransactionUnsubscribe = this.onBeforeTransaction.listen(drain);
        const onTransactionUnsubscribe = this.onTransaction.listen(drain);
        if (drainNow)
            drain();
        return (this.stopAutoFlush = () => {
            this.stopAutoFlush = undefined;
            onLocalChangesUnsubscribe();
            onBeforeTransactionUnsubscribe();
            onTransactionUnsubscribe();
        });
    }
    // ---------------------------------------------------------------- SyncStore
    subscribe = (callback) => this.onChanges.listen(() => callback());
    getSnapshot = () => this.view();
}
//# sourceMappingURL=nodes.js.map