import { ConNode } from '../const/ConNode';
import { CRDT_CONSTANTS } from '../../constants';
import { printTree } from 'tree-dump/lib/printTree';
import { compare, printTs } from '../../../json-crdt-patch/clock';
/**
 * Represents a `vec` JSON CRDT node, which is a LWW array.
 *
 * Vector is, usually a fixed length, last-write-wins array. Each element
 * in the array is a reference to another JSON CRDT node. The vector
 * can be extended by adding new elements to the end of the array.
 *
 * @category CRDT Node
 */
export class VecNode {
    doc;
    id;
    /**
     * @ignore
     */
    elements = [];
    constructor(
    /**
     * @ignore
     */
    doc, id) {
        this.doc = doc;
        this.id = id;
    }
    length() {
        return this.elements.length;
    }
    /**
     * Retrieves the ID of an element at the given index.
     *
     * @param index Index of the element to get.
     * @returns ID of the element at the given index, if any.
     */
    val(index) {
        return this.elements[index];
    }
    /**
     * Retrieves the JSON CRDT node at the given index.
     *
     * @param index Index of the element to get.
     * @returns JSON CRDT node at the given index, if any.
     */
    get(index) {
        const id = this.elements[index];
        if (!id)
            return undefined;
        return this.doc.index.get(id);
    }
    /**
     * @ignore
     */
    put(index, id) {
        if (index > CRDT_CONSTANTS.MAX_TUPLE_LENGTH)
            throw new Error('OUT_OF_BOUNDS');
        const currentId = this.val(index);
        if (currentId && compare(currentId, id) >= 0)
            return;
        if (index > this.elements.length)
            for (let i = this.elements.length; i < index; i++)
                this.elements.push(undefined);
        if (index < this.elements.length)
            this.elements[index] = id;
        else
            this.elements.push(id);
        return currentId;
    }
    // ----------------------------------------------------------------- extension
    /**
     * @ignore
     */
    __extNode = undefined;
    /**
     * @ignore
     * @returns Returns the extension data node if this is an extension node,
     *          otherwise `undefined`. The node is cached after the first access.
     */
    ext() {
        if (this.__extNode)
            return this.__extNode;
        const extensionId = this.getExtId();
        const isExtension = extensionId >= 0;
        if (!isExtension)
            return undefined;
        const extension = this.doc.ext.get(extensionId);
        if (!extension)
            return undefined;
        this.__extNode = new extension.Node(this.get(1));
        return this.__extNode;
    }
    /**
     * @ignore
     */
    isExt() {
        return !!this.ext();
    }
    /**
     * @ignore
     * @returns Returns extension ID if this is an extension node, otherwise -1.
     */
    getExtId() {
        if (this.elements.length !== 2)
            return -1;
        const type = this.get(0);
        if (!(type instanceof ConNode))
            return -1;
        const buf = type.val;
        const id = this.id;
        if (!(buf instanceof Uint8Array) || buf.length !== 3 || buf[1] !== id.sid % 256 || buf[2] !== id.time % 256)
            return -1;
        return buf[0];
    }
    /** ------------------------------------------------------ {@link JsonNode} */
    /**
     * @ignore
     */
    child() {
        return this.ext();
    }
    /**
     * @ignore
     */
    container() {
        return this;
    }
    /**
     * @ignore
     */
    children(callback) {
        const elements = this.elements;
        const length = elements.length;
        const index = this.doc.index;
        for (let i = 0; i < length; i++) {
            const id = elements[i];
            if (!id)
                continue;
            const node = index.get(id);
            if (node)
                callback(node);
        }
    }
    /**
     * @ignore
     */
    _view = [];
    /**
     * @ignore
     */
    view() {
        const extNode = this.ext();
        if (extNode)
            return extNode.view();
        let useCache = true;
        const _view = this._view;
        const arr = [];
        const index = this.doc.index;
        const elements = this.elements;
        const length = elements.length;
        for (let i = 0; i < length; i++) {
            const id = elements[i];
            const node = id ? index.get(id) : undefined;
            const value = node ? node.view() : undefined;
            if (_view[i] !== value)
                useCache = false;
            arr.push(value);
        }
        return useCache ? _view : (this._view = arr);
    }
    /**
     * @ignore
     */
    api = undefined;
    name() {
        return 'vec';
    }
    /** ----------------------------------------------------- {@link Printable} */
    toString(tab = '') {
        const extNode = this.ext();
        const header = this.name() + ' ' + printTs(this.id) + (extNode ? ` { extension = ${this.getExtId()} }` : '');
        if (extNode) {
            return this.child().toString(tab, this.id);
        }
        const index = this.doc.index;
        return (header +
            printTree(tab, [
                ...this.elements.map((id, i) => (tab) => `${i}: ${!id ? 'nil' : index.get(id) ? index.get(id).toString(tab + '  ' + ' '.repeat(('' + i).length)) : 'nil'}`),
                ...(extNode ? [(tab) => `${this.child().toString(tab)}`] : []),
            ]));
    }
}
//# sourceMappingURL=VecNode.js.map