/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
    return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _CustomArrayNodeBase_instances, _CustomArrayNodeBase_cursorFromFieldData;
import { EmptyKey, } from "../core/index.js";
import { getOrCreateNodeProxy, markContentType, prepareContentForInsert, } from "./proxies.js";
import { getFlexNode } from "./proxyBinding.js";
import { NodeKind, type, normalizeFieldSchema, } from "./schemaTypes.js";
import { cursorFromFieldData } from "./toMapTree.js";
import { TreeNodeValid } from "./types.js";
import { fail } from "../util/index.js";
import { getFlexSchema } from "./toFlexSchema.js";
import { RawTreeNode, rawError } from "./rawNode.js";
import { UsageError } from "@fluidframework/telemetry-utils/internal";
/**
 * A {@link TreeNode} which implements 'readonly T[]' and the array mutation APIs.
 * @public
 */
export const TreeArrayNode = {
    /**
     * Wrap an iterable of items to inserted as consecutive items in a array.
     * @remarks
     * The object returned by this function can be inserted into a {@link (TreeArrayNode:interface)}.
     * Its contents will be inserted consecutively in the corresponding location in the array.
     * @example
     * ```ts
     * array.insertAtEnd(TreeArrayNode.spread(iterable))
     * ```
     */
    spread: (content) => create(content),
};
/**
 * Package internal construction API.
 * Use {@link (TreeArrayNode:variable).spread} to create an instance of this type instead.
 */
let create;
/**
 * Used to insert iterable content into a {@link (TreeArrayNode:interface)}.
 * Use {@link (TreeArrayNode:variable).spread} to create an instance of this type.
 * @public
 */
export class IterableTreeArrayContent {
    constructor(content) {
        this.content = content;
    }
    /**
     * Iterates over content for nodes to insert.
     */
    [Symbol.iterator]() {
        return this.content[Symbol.iterator]();
    }
}
(() => {
    create = (content) => new IterableTreeArrayContent(content);
})();
/**
 * Given a array node proxy, returns its underlying LazySequence field.
 */
function getSequenceField(arrayNode) {
    return getFlexNode(arrayNode).getBoxed(EmptyKey);
}
// Used by 'insert*()' APIs to converts new content (expressed as a proxy union) to contextually
// typed data prior to forwarding to 'LazySequence.insert*()'.
function contextualizeInsertedArrayContent(content, sequenceField) {
    return prepareContentForInsert(content.flatMap((c) => c instanceof IterableTreeArrayContent ? Array.from(c) : [c]), sequenceField.context.checkout.forest);
}
// For compatibility, we are initially implement 'readonly T[]' by applying the Array.prototype methods
// to the array node proxy.  Over time, we should replace these with efficient implementations on LazySequence
// to avoid re-entering the proxy as these methods access 'length' and the indexed properties.
//
// For brevity, the current implementation dynamically builds a property descriptor map from a list of
// Array functions we want to re-expose via the proxy.
const arrayPrototypeKeys = [
    "concat",
    "entries",
    "every",
    "filter",
    "find",
    "findIndex",
    "flat",
    "flatMap",
    "forEach",
    "includes",
    "indexOf",
    "join",
    "keys",
    "lastIndexOf",
    "map",
    "reduce",
    "reduceRight",
    "slice",
    "some",
    "toLocaleString",
    "toString",
    "values",
    // "copyWithin",
    // "fill",
    // "length",
    // "pop",
    // "push",
    // "reverse",
    // "shift",
    // "sort",
    // "splice",
    // "unshift",
];
/**
 * {@link TreeNodeValid}, but modified to add members from Array.prototype named in {@link arrayPrototypeKeys}.
 * @privateRemarks
 * Since a lot of scratch types and values are involved with creating this,
 * it's generating using an immediately invoked function expression (IIFE).
 * This is a common JavaScript pattern for cases like this to avoid cluttering the scope.
 */
const TreeNodeWithArrayFeatures = (() => {
    /**
     * {@link TreeNodeValid}, but modified to add members from Array.prototype named in {@link arrayPrototypeKeys}.
     */
    class TreeNodeWithArrayFeaturesUntyped extends TreeNodeValid {
    }
    // Modify TreeNodeWithArrayFeaturesUntyped to add the members from Array.prototype
    arrayPrototypeKeys.forEach((key) => {
        Object.defineProperty(TreeNodeWithArrayFeaturesUntyped.prototype, key, {
            value: Array.prototype[key],
        });
    });
    return TreeNodeWithArrayFeaturesUntyped;
})();
/* eslint-enable @typescript-eslint/explicit-member-accessibility */
/**
 * Attempts to coerce the given property key to an integer index property.
 * @param key - The property key to coerce.
 * @param exclusiveMax - This restricts the range in which the resulting index is allowed to be.
 * The coerced index of `key` must be less than `exclusiveMax` or else this function will return `undefined`.
 * This is useful for reading an array within the bounds of its length, e.g. `asIndex(key, array.length)`.
 */
export function asIndex(key, exclusiveMax) {
    if (typeof key !== "string") {
        return undefined;
    }
    // TODO: It may be worth a '0' <= ch <= '9' check before calling 'Number' to quickly
    // reject 'length' as an index, or even parsing integers ourselves.
    const asNumber = Number(key);
    if (!Number.isInteger(asNumber)) {
        return undefined;
    }
    // Check that the original string is the same after converting to a number and back again.
    // This prevents keys like "5.0", "0x5", " 5" from coercing to 5, and keys like " " or "" from coercing to 0.
    const asString = String(asNumber);
    if (asString !== key) {
        return undefined;
    }
    // TODO: See 'matrix/range.ts' for fast integer coercing + range check.
    return 0 <= asNumber && asNumber < exclusiveMax ? asNumber : undefined;
}
/**
 * @param allowAdditionalProperties - If true, setting of unexpected properties will be forwarded to the target object.
 * Otherwise setting of unexpected properties will error.
 * @param proxyTarget - Target object of the proxy. Must provide an own `length` value property
 * (which is not used but must exist for getOwnPropertyDescriptor invariants) and the array functionality from {@link arrayNodePrototype}.
 * Controls the prototype exposed by the produced proxy.
 * @param dispatchTarget - provides the functionally of the node, implementing all fields.
 */
function createArrayNodeProxy(allowAdditionalProperties, proxyTarget, dispatchTarget) {
    // To satisfy 'deepEquals' level scrutiny, the target of the proxy must be an array literal in order
    // to pass 'Object.getPrototypeOf'.  It also satisfies 'Array.isArray' and 'Object.prototype.toString'
    // requirements without use of Array[Symbol.species], which is potentially on a path ot deprecation.
    const proxy = new Proxy(proxyTarget, {
        get: (target, key, receiver) => {
            const field = getSequenceField(receiver);
            const maybeIndex = asIndex(key, field.length);
            if (maybeIndex === undefined) {
                if (key === "length") {
                    return field.length;
                }
                // Pass the proxy as the receiver here, so that any methods on
                // the prototype receive `proxy` as `this`.
                return Reflect.get(dispatchTarget, key, receiver);
            }
            const value = field.boxedAt(maybeIndex);
            if (value === undefined) {
                return undefined;
            }
            // TODO: Ideally, we would return leaves without first boxing them.  However, this is not
            //       as simple as calling '.content' since this skips the node and returns the FieldNode's
            //       inner field.
            return getOrCreateNodeProxy(value);
        },
        set: (target, key, newValue, receiver) => {
            if (key === "length") {
                // To allow "length" to look like "length" on an array, getOwnPropertyDescriptor has to report it as a writable value.
                // This means the proxy target must provide a length value, but since it can't use getters and setters, it can't be correct.
                // Therefor length has to be handled in this proxy.
                // Since its not actually mutable, return false so setting it will produce a type error.
                return false;
            }
            // 'Symbol.isConcatSpreadable' may be set on an Array instance to modify the behavior of
            // the concat method.  We allow this property to be added to the dispatch object.
            if (key === Symbol.isConcatSpreadable) {
                return Reflect.set(dispatchTarget, key, newValue, receiver);
            }
            // Array nodes treat all non-negative integer indexes as array access.
            // Using Infinity here (rather than length) ensures that indexing past the end doesn't create additional session local properties.
            const maybeIndex = asIndex(key, Number.POSITIVE_INFINITY);
            if (maybeIndex !== undefined) {
                // For MVP, we otherwise disallow setting properties (mutation is only available via the array node mutation APIs).
                return false;
            }
            return allowAdditionalProperties ? Reflect.set(target, key, newValue) : false;
        },
        has: (target, key) => {
            const field = getSequenceField(proxy);
            const maybeIndex = asIndex(key, field.length);
            return maybeIndex !== undefined || Reflect.has(dispatchTarget, key);
        },
        ownKeys: (target) => {
            const field = getSequenceField(proxy);
            // TODO: Would a lazy iterator to produce the indexes work / be more efficient?
            // TODO: Need to surface 'Symbol.isConcatSpreadable' as an own key.
            const keys = Array.from({ length: field.length }, (_, index) => `${index}`);
            if (allowAdditionalProperties) {
                keys.push(...Reflect.ownKeys(target));
            }
            else {
                keys.push("length");
            }
            return keys;
        },
        getOwnPropertyDescriptor: (target, key) => {
            const field = getSequenceField(proxy);
            const maybeIndex = asIndex(key, field.length);
            if (maybeIndex !== undefined) {
                const val = field.boxedAt(maybeIndex);
                // To satisfy 'deepEquals' level scrutiny, the property descriptor for indexed properties must
                // be a simple value property (as opposed to using getter) and declared writable/enumerable/configurable.
                return {
                    // TODO: Ideally, we would return leaves without first boxing them.  However, this is not
                    //       as simple as calling '.at' since this skips the node and returns the FieldNode's
                    //       inner field.
                    value: val === undefined ? val : getOrCreateNodeProxy(val),
                    writable: true,
                    enumerable: true,
                    configurable: true,
                };
            }
            else if (key === "length") {
                // To satisfy 'deepEquals' level scrutiny, the property descriptor for 'length' must be a simple
                // value property (as opposed to using getter) and be declared writable / non-configurable.
                return {
                    value: field.length,
                    writable: true,
                    enumerable: false,
                    configurable: false,
                };
            }
            return Reflect.getOwnPropertyDescriptor(dispatchTarget, key);
        },
    });
    return proxy;
}
class CustomArrayNodeBase extends TreeNodeWithArrayFeatures {
    constructor() {
        super(...arguments);
        _CustomArrayNodeBase_instances.add(this);
    }
    toJSON() {
        // This override causes the class instance to `JSON.stringify` as `[a, b]` rather than `{0: a, 1: b}`.
        return Array.from(this);
    }
    // Instances of this class are used as the dispatch object for the proxy,
    // and thus its set of keys is used to implement `has` (for the `in` operator) for the non-numeric cases.
    // Therefore it must include `length`,
    // even though this "length" is never invoked (due to being shadowed by the proxy provided own property).
    get length() {
        return fail("Proxy should intercept length");
    }
    [(_CustomArrayNodeBase_instances = new WeakSet(), _CustomArrayNodeBase_cursorFromFieldData = function _CustomArrayNodeBase_cursorFromFieldData(value) {
        const content = contextualizeInsertedArrayContent(value, getSequenceField(this));
        // TODO: this is not valid since this is a value field schema, not a sequence one (which does not exist in the simple tree layer),
        // but it works since cursorFromFieldData special cases arrays.
        const simpleFieldSchema = normalizeFieldSchema(this.simpleSchema);
        return cursorFromFieldData(content, simpleFieldSchema, getFlexNode(this).context.nodeKeyManager);
    }, Symbol.iterator)]() {
        return this.values();
    }
    get [Symbol.unscopables]() {
        // This might not be the exact right set of values, but it only matters for `with` clauses which are deprecated and are banned in strict mode, so it shouldn't matter much.
        // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with for details.
        return Array.prototype[Symbol.unscopables];
    }
    at(index) {
        const field = getSequenceField(this);
        const val = field.boxedAt(index);
        if (val === undefined) {
            return val;
        }
        return getOrCreateNodeProxy(val);
    }
    insertAt(index, ...value) {
        getSequenceField(this).insertAt(index, __classPrivateFieldGet(this, _CustomArrayNodeBase_instances, "m", _CustomArrayNodeBase_cursorFromFieldData).call(this, value));
    }
    insertAtStart(...value) {
        getSequenceField(this).insertAtStart(__classPrivateFieldGet(this, _CustomArrayNodeBase_instances, "m", _CustomArrayNodeBase_cursorFromFieldData).call(this, value));
    }
    insertAtEnd(...value) {
        getSequenceField(this).insertAtEnd(__classPrivateFieldGet(this, _CustomArrayNodeBase_instances, "m", _CustomArrayNodeBase_cursorFromFieldData).call(this, value));
    }
    removeAt(index) {
        getSequenceField(this).removeAt(index);
    }
    removeRange(start, end) {
        const field = getSequenceField(this);
        const fieldEditor = field.sequenceEditor();
        const { length } = field;
        const removeStart = start ?? 0;
        const removeEnd = Math.min(length, end ?? length);
        validatePositiveIndex(removeStart);
        validatePositiveIndex(removeEnd);
        if (removeEnd < removeStart) {
            // This catches both the case where start is > array.length and when start is > end.
            throw new UsageError('Too large of "start" value passed to TreeArrayNode.removeRange.');
        }
        fieldEditor.remove(removeStart, removeEnd - removeStart);
    }
    moveToStart(sourceIndex, source) {
        if (source !== undefined) {
            getSequenceField(this).moveToStart(sourceIndex, getSequenceField(source));
        }
        else {
            getSequenceField(this).moveToStart(sourceIndex);
        }
    }
    moveToEnd(sourceIndex, source) {
        if (source !== undefined) {
            getSequenceField(this).moveToEnd(sourceIndex, getSequenceField(source));
        }
        else {
            getSequenceField(this).moveToEnd(sourceIndex);
        }
    }
    moveToIndex(index, sourceIndex, source) {
        if (source !== undefined) {
            getSequenceField(this).moveToIndex(index, sourceIndex, getSequenceField(source));
        }
        else {
            getSequenceField(this).moveToIndex(index, sourceIndex);
        }
    }
    moveRangeToStart(sourceStart, sourceEnd, source) {
        if (source !== undefined) {
            getSequenceField(this).moveRangeToStart(sourceStart, sourceEnd, getSequenceField(source));
        }
        else {
            getSequenceField(this).moveRangeToStart(sourceStart, sourceEnd);
        }
    }
    moveRangeToEnd(sourceStart, sourceEnd, source) {
        if (source !== undefined) {
            getSequenceField(this).moveRangeToEnd(sourceStart, sourceEnd, getSequenceField(source));
        }
        else {
            getSequenceField(this).moveRangeToEnd(sourceStart, sourceEnd);
        }
    }
    moveRangeToIndex(index, sourceStart, sourceEnd, source) {
        if (source !== undefined) {
            getSequenceField(this).moveRangeToIndex(index, sourceStart, sourceEnd, getSequenceField(source));
        }
        else {
            getSequenceField(this).moveRangeToIndex(index, sourceStart, sourceEnd);
        }
    }
}
CustomArrayNodeBase.kind = NodeKind.Array;
/**
 * Define a {@link TreeNodeSchema} for a {@link (TreeArrayNode:interface)}.
 *
 * @param name - Unique identifier for this schema including the factory's scope.
 */
export function arraySchema(identifier, info, implicitlyConstructable, customizable) {
    let flexSchema;
    // This class returns a proxy from its constructor to handle numeric indexing.
    // Alternatively it could extend a normal class which gets tons of numeric properties added.
    class schema extends CustomArrayNodeBase {
        static prepareInstance(instance, flexNode) {
            const proxyTarget = customizable ? instance : [];
            if (customizable) {
                // Since proxy reports this as a "non-configurable" property, it must exist on the underlying object used as the proxy target, not as an inherited property.
                // This should not get used as the proxy should intercept all use.
                Object.defineProperty(instance, "length", {
                    value: NaN,
                    writable: true,
                    enumerable: false,
                    configurable: false,
                });
            }
            return createArrayNodeProxy(customizable, proxyTarget, instance);
        }
        static buildRawNode(instance, input) {
            return new RawFieldNode(flexSchema, copyContent(flexSchema.name, input));
        }
        static oneTimeSetup() {
            flexSchema = getFlexSchema(this);
        }
        get [type]() {
            return identifier;
        }
        get simpleSchema() {
            return info;
        }
    }
    schema.constructorCached = undefined;
    schema.identifier = identifier;
    schema.info = info;
    schema.implicitlyConstructable = implicitlyConstructable;
    return schema;
}
/**
 * The implementation of a field node created by {@link createRawNode}.
 */
class RawFieldNode extends RawTreeNode {
    get content() {
        throw rawError("Reading content of an array node");
    }
    get boxedContent() {
        throw rawError("Reading boxed content of an array node");
    }
}
function copyContent(typeName, content) {
    const copy = Array.from(content);
    markContentType(typeName, copy);
    return copy;
}
function validateSafeInteger(index) {
    if (!Number.isSafeInteger(index)) {
        throw new UsageError(`Expected a safe integer, got ${index}.`);
    }
}
function validatePositiveIndex(index) {
    validateSafeInteger(index);
    if (index < 0) {
        throw new UsageError(`Expected non-negative index, got ${index}.`);
    }
}
//# sourceMappingURL=arrayNode.js.map