"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
var _a, _b;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PrefixedPath = exports.prefixPathPrefix = exports.prefixFieldPath = exports.prefixPath = exports.SynchronousCursor = exports.singleStackTreeCursor = void 0;
const core_utils_1 = require("@fluidframework/core-utils");
const core_1 = require("../core");
const util_1 = require("../util");
/**
 * Create a cursor, in `nodes` mode at the root of the provided tree.
 *
 * @returns an {@link ITreeCursorSynchronous} for a single root.
 * @alpha
 */
function singleStackTreeCursor(root, adapter) {
    return new StackCursor(adapter, [], [], [root], 0);
}
exports.singleStackTreeCursor = singleStackTreeCursor;
/**
 * A class that satisfies part of the ITreeCursorSynchronous implementation.
 */
class SynchronousCursor {
    constructor() {
        this[_a] = true;
        this.pending = false;
    }
    skipPendingFields() {
        return true;
    }
}
exports.SynchronousCursor = SynchronousCursor;
_a = core_1.CursorMarker;
/**
 * A simple general purpose ITreeCursorSynchronous implementation.
 *
 * As this is a generic implementation, it's ability to optimize is limited.
 *
 * Note that TNode can be `null` (and we should support `undefined` as well),
 * so be careful using types like `TNode | undefined` and expressions like `TNode ??`.
 */
class StackCursor extends SynchronousCursor {
    /**
     * Might start at special root where fields are detached sequences.
     *
     * @param adapter - policy logic.
     * @param siblingStack - Stack of collections of siblings along the path through the tree:
     * does not include current level (which is stored in `siblings`).
     * Even levels in the stack (starting from 0) are sequences of nodes and odd levels
     * are for fields keys on a node.
     * @param indexStack - Stack of indices into the corresponding levels in `siblingStack`.
     * @param siblings - Siblings at the current level (not included in `siblingStack`).
     * @param index - Index into `siblings`.
     */
    constructor(adapter, siblingStack, indexStack, siblings, index) {
        super();
        this.adapter = adapter;
        this.siblingStack = siblingStack;
        this.indexStack = indexStack;
        this.siblings = siblings;
        this.index = index;
        this[_b] = true;
        this.chunkLength = 1;
    }
    getFieldKey() {
        // assert(this.mode === CursorLocationType.Fields, "must be in fields mode");
        return this.siblings[this.index];
    }
    getStackedFieldKey(height) {
        (0, core_utils_1.assert)(height % 2 === 1, 0x3b8 /* must field height */);
        return this.siblingStack[height][this.indexStack[height]];
    }
    getStackedNodeIndex(height) {
        // assert(height % 2 === 0, "must be node height");
        return this.indexStack[height];
    }
    getStackedNode(height) {
        const index = this.getStackedNodeIndex(height);
        return this.siblingStack[height][index];
    }
    getFieldLength() {
        // assert(this.mode === CursorLocationType.Fields, "must be in fields mode");
        return this.getField().length;
    }
    enterNode(index) {
        // assert(this.mode === CursorLocationType.Fields, "must be in fields mode");
        const siblings = this.getField();
        (0, core_utils_1.assert)(index in siblings, 0x405 /* child must exist at index */);
        this.siblingStack.push(this.siblings);
        this.indexStack.push(this.index);
        this.index = index;
        this.siblings = siblings;
    }
    getPath(prefix) {
        (0, core_utils_1.assert)(this.mode === 0 /* CursorLocationType.Nodes */, 0x3b9 /* must be in nodes mode */);
        return this.getOffsetPath(0, prefix);
    }
    getFieldPath(prefix) {
        (0, core_utils_1.assert)(this.mode === 1 /* CursorLocationType.Fields */, 0x449 /* must be in fields mode */);
        return {
            field: this.indexStack.length === 1
                ? prefix?.rootFieldOverride ?? this.getFieldKey()
                : this.getFieldKey(),
            parent: this.getOffsetPath(1, prefix),
        };
    }
    getOffsetPath(offset, prefix) {
        // It is more efficient to handle prefix directly in here rather than delegating to PrefixedPath.
        const length = this.indexStack.length - offset;
        if (length === 0) {
            return prefix?.parent; // At root
        }
        (0, core_utils_1.assert)(length > 0, 0x44a /* invalid offset to above root */);
        (0, core_utils_1.assert)(length % 2 === 0, 0x44b /* offset path must point to node not field */);
        const getIndex = (height) => {
            let parentIndex = height === this.indexStack.length ? this.index : this.getStackedNodeIndex(height);
            if (prefix !== undefined && height === 2) {
                parentIndex += prefix.indexOffset ?? 0;
            }
            return parentIndex;
        };
        // Perf Note:
        // This is O(depth) in tree.
        // If many different anchors are created, this could be optimized to amortize the costs.
        // For example, the cursor could cache UpPaths from the anchorSet when creating an anchor,
        // then reuse them as a starting point when making another.
        // Could cache this at one depth, and remember the depth.
        // When navigating up, adjust cached anchor if present.
        let path = prefix?.parent;
        // Skip top level, since root node in path is "undefined" and does not have a parent or index.
        for (let height = 2; height <= length; height += 2) {
            const fieldOverride = height === 2 ? prefix?.rootFieldOverride : undefined;
            path = {
                parent: path,
                parentIndex: getIndex(height),
                parentField: fieldOverride ?? this.getStackedFieldKey(height - 1),
            };
        }
        return path;
    }
    fork() {
        // Siblings arrays are not modified during navigation and do not need be be copied.
        // This allows this copy to be shallow, and `this.siblings` below to not be copied as all.
        return new StackCursor(this.adapter, [...this.siblingStack], [...this.indexStack], this.siblings, this.index);
    }
    enterField(key) {
        // assert(this.mode === CursorLocationType.Nodes, "must be in nodes mode");
        this.siblingStack.push(this.siblings);
        this.indexStack.push(this.index);
        // For fields, siblings are only used for key lookup and
        // nextField and which has arbitrary iteration order,
        // so making a array of just key here works.
        // This adds an allocation, so it's optimizing code simplicity and for the other use case (enumeration)
        // at the cost of an allocation here.
        this.index = 0;
        this.siblings = [key];
    }
    get mode() {
        return this.siblingStack.length % 2 === 0
            ? 0 /* CursorLocationType.Nodes */
            : 1 /* CursorLocationType.Fields */;
    }
    nextField() {
        this.index += 1;
        if (this.index === this.siblings.length) {
            this.exitField();
            return false;
        }
        return true;
    }
    firstField() {
        // assert(this.mode === CursorLocationType.Nodes, "must be in nodes mode");
        const fields = this.adapter.keysFromNode(this.getNode());
        if (fields.length === 0) {
            return false;
        }
        this.siblingStack.push(this.siblings);
        this.indexStack.push(this.index);
        this.index = 0;
        this.siblings = fields;
        return true;
    }
    seekNodes(offset) {
        // assert(this.mode === CursorLocationType.Nodes, "can only seekNodes when in Nodes");
        this.index += offset;
        if (this.index in this.siblings) {
            return true;
        }
        this.exitNode();
        return false;
    }
    firstNode() {
        // assert(this.mode === CursorLocationType.Fields, "firstNode only allowed in fields mode");
        const nodes = this.getField();
        if (nodes.length === 0) {
            return false;
        }
        this.siblingStack.push(this.siblings);
        this.indexStack.push(this.index);
        this.index = 0;
        this.siblings = nodes;
        return true;
    }
    nextNode() {
        (0, core_utils_1.assert)(this.mode === 0 /* CursorLocationType.Nodes */, 0x406 /* can only nextNode when in Nodes */);
        this.index++;
        if (this.index < this.siblings.length) {
            return true;
        }
        this.exitNode();
        return false;
    }
    exitField() {
        // assert(this.mode === CursorLocationType.Fields, "can only navigate up from field when in field");
        this.siblings = this.siblingStack.pop() ?? (0, util_1.fail)("Unexpected siblingStack.length");
        this.index = this.indexStack.pop() ?? (0, util_1.fail)("Unexpected indexStack.length");
    }
    exitNode() {
        // assert(this.mode === CursorLocationType.Nodes, "can only navigate up from node when in node");
        this.siblings = this.siblingStack.pop() ?? (0, util_1.fail)("Unexpected siblingStack.length");
        this.index = this.indexStack.pop() ?? (0, util_1.fail)("Unexpected indexStack.length");
    }
    getNode() {
        // assert(this.mode === CursorLocationType.Nodes, "can only get node when in node");
        return this.siblings[this.index];
    }
    getField() {
        // assert(this.mode === CursorLocationType.Fields, "can only get field when in fields");
        const parent = this.getStackedNode(this.indexStack.length - 1);
        const key = this.getFieldKey();
        const field = this.adapter.getFieldFromNode(parent, key);
        return field;
    }
    /**
     * @returns the value of the current node
     */
    get value() {
        return this.adapter.value(this.getNode());
    }
    /**
     * @returns the type of the current node
     */
    get type() {
        return this.adapter.type(this.getNode());
    }
    get fieldIndex() {
        // assert(this.mode === CursorLocationType.Nodes, "can only node's index when in node");
        return this.index;
    }
    get chunkStart() {
        return this.fieldIndex;
    }
}
_b = core_1.CursorMarker;
/**
 * Apply `prefix` to `path`.
 * @alpha
 */
function prefixPath(prefix, path) {
    if (prefix === undefined) {
        return path;
    }
    if (prefix.parent === undefined &&
        prefix.rootFieldOverride === undefined &&
        (prefix.indexOffset ?? 0) === 0) {
        return path;
    }
    return applyPrefix(prefix, path);
}
exports.prefixPath = prefixPath;
/**
 * Apply `prefix` to `path`.
 * @alpha
 */
function prefixFieldPath(prefix, path) {
    if (prefix === undefined) {
        return path;
    }
    if (prefix.parent === undefined &&
        prefix.rootFieldOverride === undefined &&
        (prefix.indexOffset ?? 0) === 0) {
        return path;
    }
    return {
        field: path.parent === undefined ? prefix.rootFieldOverride ?? path.field : path.field,
        parent: prefixPath(prefix, path.parent),
    };
}
exports.prefixFieldPath = prefixFieldPath;
/**
 * Compose two prefixes together.
 * `prefixFieldPath(root, prefixFieldPath(inner, path))` should be the same as `prefixFieldPath(prefixPathPrefix(root, inner), path))`
 *
 * TODO: tests for this.
 */
function prefixPathPrefix(root, inner) {
    if (inner.parent !== undefined) {
        const composedPrefix = {
            parent: new PrefixedPath(root, inner.parent),
            rootFieldOverride: inner.rootFieldOverride,
            indexOffset: inner.indexOffset,
        };
        return composedPrefix;
    }
    else {
        const composedPrefix = {
            parent: root.parent,
            rootFieldOverride: root.rootFieldOverride ?? inner.rootFieldOverride,
            indexOffset: (inner.indexOffset ?? 0) + (root.indexOffset ?? 0),
        };
        return composedPrefix;
    }
}
exports.prefixPathPrefix = prefixPathPrefix;
function applyPrefix(prefix, path) {
    if (path === undefined) {
        return prefix.parent;
    }
    else {
        // As an optimization, avoid double wrapping paths with multiple prefixes
        if (path instanceof PrefixedPath) {
            const composedPrefix = prefixPathPrefix(prefix, path.prefix);
            return new PrefixedPath(composedPrefix, path.path);
        }
        else {
            return new PrefixedPath(prefix, path);
        }
    }
}
/**
 * Wrapper around a path that adds a prefix to the root.
 *
 * Exported for testing: use `prefixPath` and `prefixFieldPath` to construct.
 */
class PrefixedPath {
    constructor(prefix, path) {
        this.prefix = prefix;
        this.path = path;
        if (path.parent === undefined) {
            this.parentField = prefix.rootFieldOverride ?? path.parentField;
            this.parentIndex = path.parentIndex + (prefix.indexOffset ?? 0);
        }
        else {
            this.parentField = path.parentField;
            this.parentIndex = path.parentIndex;
        }
    }
    get parent() {
        return applyPrefix(this.prefix, this.path.parent);
    }
}
exports.PrefixedPath = PrefixedPath;
//# sourceMappingURL=treeCursorUtils.js.map