"use strict";
/*!
 * 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);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TableSchema = exports.System_TableSchema = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const internal_2 = require("@fluidframework/telemetry-utils/internal");
const index_js_1 = require("./core/index.js");
const index_js_2 = require("./shared-tree/index.js");
const index_js_3 = require("./simple-tree/index.js");
const index_js_4 = require("./util/index.js");
/**
 * Not intended for use outside of this package.
 *
 * @privateRemarks
 * This namespace is a collection of internal system types relate to {@link TableSchema}.
 * This namespace should be strictly type-exported by the package.
 * All members should be tagged with `@system`.
 *
 * Orphaned Cells:
 * Without safeguards, it is possible for cells to become "orphaned".
 * An orphaned cell is a cell that does not correspond to a valid row and column.
 * In order to preserve the invariant that all cells must have a valid row and column, table operations
 * (eg, inserting/removing rows/columns, or setting/removing a cell) will automatically include constraints that
 * guard transactions from producing orphaned cells.
 *
 * @system @beta
 */
var System_TableSchema;
(function (System_TableSchema) {
    /**
     * Factory for creating column schema.
     * @system @beta
     */
    // eslint-disable-next-line @typescript-eslint/explicit-function-return-type -- Return type is too complex to be reasonable to specify
    function createColumnSchema(inputSchemaFactory, propsSchema) {
        const schemaFactory = createTableScopedFactory(inputSchemaFactory);
        // Note: `columnFields` is broken into two parts to work around a TypeScript bug
        // that results in broken `.d.ts` output.
        // See definition of `ColumnInsertableType` below.
        const columnFieldsBuiltInParts = {
            id: schemaFactory.identifier,
        };
        const columnFieldsPropsPart = {
            props: propsSchema,
        };
        /**
         * {@link Column} fields.
         *
         * @remarks
         * Extracted for re-use in returned type signature defined later in this function.
         * The implicit typing is intentional.
         *
         * Note: ideally we would add a satisfies clause here to ensure that this satisfies
         * `Record<string, ImplicitFieldSchema>`, but doing so causes TypeScript to prematurely and incorrectly evaluate the type of `propsSchema`.
         * Likely related to the following issue: https://github.com/microsoft/TypeScript/issues/52394
         */
        const columnFields = {
            ...columnFieldsBuiltInParts,
            ...columnFieldsPropsPart,
        }; // satisfies Record<string, ImplicitFieldSchema>;
        /**
         * A column in a table.
         */
        class Column extends schemaFactory.object("Column", columnFields, {
            // Will make it easier to evolve this schema in the future.
            allowUnknownOptionalFields: true,
        }) {
        }
        // Returning SingletonSchema without a type conversion results in TypeScript generating something like `readonly "__#124291@#brand": unknown;`
        // for the private brand field of TreeNode.
        // This numeric id doesn't seem to be stable over incremental builds, and thus causes diffs in the API extractor reports.
        // This is avoided by doing this type conversion.
        // The conversion is done via assignment instead of `as` to get stronger type safety.
        const ColumnSchemaType = Column;
        return ColumnSchemaType;
    }
    System_TableSchema.createColumnSchema = createColumnSchema;
    /**
     * Factory for creating row schema.
     * @system @beta
     */
    // eslint-disable-next-line @typescript-eslint/explicit-function-return-type -- Return type is too complex to be reasonable to specify
    function createRowSchema(inputSchemaFactory, cellSchema, propsSchema) {
        const schemaFactory = createTableScopedFactory(inputSchemaFactory);
        // Note: `rowFields` is broken into two parts to work around a TypeScript bug
        // that results in broken `.d.ts` output.
        // See definition of `RowInsertableType` below.
        const rowFieldsBuiltInParts = {
            id: schemaFactory.identifier,
            /**
             * The cells of the table row, keyed by column ID.
             * @remarks
             * The table row models its cells as a record, where each key is the ID of the column it belongs to. The choice of record (as opposed to a map) is intended to make interop with common table rendering libraries in TypeScript/JavaScript easier.
             */
            cells: schemaFactory.required(schemaFactory.record("Row.cells", cellSchema), {
                metadata: {
                    description: "The cells of the table row, keyed by column ID.",
                },
            }),
        };
        const rowFieldsPropsPart = {
            props: propsSchema,
        };
        /**
         * {@link RowSchema} fields.
         * @remarks Extracted for re-use in returned type signature defined later in this function.
         * The implicit typing is intentional.
         * Note: ideally we would add a satisfies clause here to ensure that this satisfies
         * `Record<string, ImplicitFieldSchema>`, but doing so causes TypeScript to prematurely and incorrectly evaluate the type of `propsSchema`.
         * Likely related to the following issue: https://github.com/microsoft/TypeScript/issues/52394
         */
        const rowFields = {
            ...rowFieldsBuiltInParts,
            ...rowFieldsPropsPart,
        }; // satisfies Record<string, ImplicitFieldSchema>;
        /**
         * The Row schema - this is a map of Cells where the key is the column id
         */
        class RowSchema extends schemaFactory.object("Row", rowFields, {
            // Will make it easier to evolve this schema in the future.
            allowUnknownOptionalFields: true,
        }) {
        }
        // Returning SingletonSchema without a type conversion results in TypeScript generating something like `readonly "__#124291@#brand": unknown;`
        // for the private brand field of TreeNode.
        // This numeric id doesn't seem to be stable over incremental builds, and thus causes diffs in the API extractor reports.
        // This is avoided by doing this type conversion.
        // The conversion is done via assignment instead of `as` to get stronger type safety.
        const RowSchemaType = RowSchema;
        return RowSchemaType;
    }
    System_TableSchema.createRowSchema = createRowSchema;
    /**
     * Factory for creating table schema.
     * @system @beta
     */
    // eslint-disable-next-line @typescript-eslint/explicit-function-return-type -- Return type is too complex to be reasonable to specify
    function createTableSchema(inputSchemaFactory, _cellSchema, columnSchema, rowSchema) {
        var _Table_instances, _a, _Table_applyEditsInBatch, _Table_tryGetColumn, _Table_getColumn, _Table_tryGetRow, _Table_getRow, _Table_validateNewColumns, _Table_validateNewRows;
        const schemaFactory = createTableScopedFactory(inputSchemaFactory);
        /**
         * {@link Table} inner fields.
         * @remarks Extracted for re-use as construction parameters.
         * @see {@link Table.create}.
         */
        const tableInnerFields = {
            rows: schemaFactory.array("Table.rows", rowSchema),
            columns: schemaFactory.array("Table.columns", columnSchema),
        };
        /**
         * {@link Table} fields.
         * @remarks Extracted for re-use in returned type signature defined later in this function.
         * The implicit typing is intentional.
         */
        const tableFields = {
            table: schemaFactory.required(schemaFactory.object("Table", tableInnerFields), {
                // Use an empty key in the stored schema format to help prevent schema-unaware edits from being
                // made to the table, which may violate intended invariants.
                key: index_js_1.EmptyKey,
            }),
        };
        /**
         * The Table schema
         */
        class Table
        // Calling the objectSchema factory directly rather than using the schemaFactory so we can specify
        // `implicitlyConstructable: false`, which the SchemaFactory APIs do not yet support.
        // TODO: when support for configuring `implicitlyConstructable` is added to SchemaFactory, switch to
        // using that instead.
         extends (0, index_js_3.objectSchema)(
        /* identifier: */ (0, index_js_3.scoped)(schemaFactory, "TableRoot"), 
        /* info: */ tableFields, 
        /* implicitlyConstructable: */ false, 
        /* nodeOptions: */ {
            // Will make it easier to evolve this schema in the future.
            allowUnknownOptionalFields: true,
        }) {
            constructor(node) {
                super(node ?? { table: { columns: [], rows: [] } });
                _Table_instances.add(this);
            }
            static create(initialContents) {
                // #region Input validation
                const columns = initialContents?.columns ?? [];
                const columnIds = new Set();
                for (const column of columns) {
                    columnIds.add(column.id);
                }
                const rows = initialContents?.rows ?? [];
                _a._validateNewColumns(columns, 
                // New table, so no existing columns to validate against
                new Set());
                _a._validateNewRows(rows, 
                // New table, so no existing rows to validate against
                new Set(), 
                // No existing columns to validate cells against, but we do need to validate the new rows against the new columns
                columnIds);
                // #endregion
                return new this(initialContents === undefined
                    ? undefined
                    : {
                        table: initialContents,
                        // We have typed our constructor to prevent users from calling it directly with insertable contents.
                        // But the base constructor still allows it. Cast to work around this.
                    });
            }
            get columns() {
                return this.table.columns;
            }
            get rows() {
                return this.table.rows;
            }
            getColumn(indexOrId) {
                return __classPrivateFieldGet(this, _Table_instances, "m", _Table_tryGetColumn).call(this, indexOrId);
            }
            getRow(indexOrId) {
                return __classPrivateFieldGet(this, _Table_instances, "m", _Table_tryGetRow).call(this, indexOrId);
            }
            getCell(key) {
                const { column: columnOrIdOrIndex, row: rowOrIdOrIndex } = key;
                const row = __classPrivateFieldGet(this, _Table_instances, "m", _Table_tryGetRow).call(this, rowOrIdOrIndex);
                if (row === undefined) {
                    return undefined;
                }
                const column = __classPrivateFieldGet(this, _Table_instances, "m", _Table_tryGetColumn).call(this, columnOrIdOrIndex);
                if (column === undefined) {
                    return undefined;
                }
                return row.cells[column.id];
            }
            insertColumns({ columns, index, }) {
                // #region Input validation
                // Ensure specified index is valid
                if (index !== undefined) {
                    (0, index_js_4.validateIndex)(index, this.table.columns, "Table.insertColumns", true);
                }
                // Ensure the new columns being inserted are valid
                __classPrivateFieldGet(this, _Table_instances, "m", _Table_validateNewColumns).call(this, columns);
                // #endregion
                __classPrivateFieldGet(this, _Table_instances, "m", _Table_applyEditsInBatch).call(this, {
                    applyEdits: () => {
                        // TypeScript is unable to narrow the column type correctly here, hence the casts below.
                        // See: https://github.com/microsoft/TypeScript/issues/52144
                        if (index === undefined) {
                            // eslint-disable-next-line @typescript-eslint/no-explicit-any
                            this.table.columns.insertAtEnd(index_js_3.TreeArrayNode.spread(columns));
                        }
                        else {
                            // eslint-disable-next-line @typescript-eslint/no-explicit-any
                            this.table.columns.insertAt(index, index_js_3.TreeArrayNode.spread(columns));
                        }
                    },
                    // Relevant invariant: each cell corresponds to an existing row and column
                    // Scenarios that this constraint is intended to prevent:
                    //  * Client A inserts a column, then client B adds row with a cell for that column, then client A reverts the column insertion.
                    //  * Client A inserts a column, then client B populates a cell for that column within an existing row, then client A reverts the column insertion.
                    //  Notes:
                    //  * In either scenario, A and B may be the same client.
                    //  * In either scenario, B's edit and the revert may or may not be concurrent.
                    // Collateral scenarios that this constraint also prevents:
                    //  * Any other scenario where client A inserts a column, then client B edits _any_ part of the tree, then client A reverts the column insertion.
                    // Future improvements:
                    // Use both...
                    //  * A "no attach on revert" constraint on the row array
                    //  * A "no shallow change" constraint on every cell that corresponds to the new column in every existing row
                    preconditionsOnRevert: [{ type: "noChange" }],
                });
                // Inserting the input nodes into the tree hydrates them, making them usable as nodes.
                return columns;
            }
            insertRows({ index, rows, }) {
                // #region Input validation
                // Ensure specified index is valid
                if (index !== undefined) {
                    (0, index_js_4.validateIndex)(index, this.table.rows, "Table.insertRows", true);
                }
                // Ensure the new rows being inserted are valid
                __classPrivateFieldGet(this, _Table_instances, "m", _Table_validateNewRows).call(this, rows);
                // #endregion
                // Relevant invariant: each cell corresponds to an existing row and column
                // Prevents cell leaks from concurrently removed columns.
                // Example scenario: Client A removes a column while concurrently Client B adds a row with cells for those columns (including the one A removed).
                // If client B is sequenced after A, then B's row could have cells that do not correspond to existing columns.
                // This constraint ensures all columns that existed when creating the row still exist when the row insertion is applied.
                // TODO: Replace with "no detach" constraint on the column array when available.
                const columnConstraints = this.table.columns.map((column) => ({
                    type: "nodeInDocument",
                    node: column,
                }));
                __classPrivateFieldGet(this, _Table_instances, "m", _Table_applyEditsInBatch).call(this, {
                    applyEdits: () => {
                        // TypeScript is unable to narrow the row type correctly here, hence the casts below.
                        // See: https://github.com/microsoft/TypeScript/issues/52144
                        if (index === undefined) {
                            // eslint-disable-next-line @typescript-eslint/no-explicit-any
                            this.table.rows.insertAtEnd(index_js_3.TreeArrayNode.spread(rows));
                        }
                        else {
                            // eslint-disable-next-line @typescript-eslint/no-explicit-any
                            this.table.rows.insertAt(index, index_js_3.TreeArrayNode.spread(rows));
                        }
                    },
                    preconditions: columnConstraints.length > 0 ? columnConstraints : undefined,
                });
                // Inserting the input nodes into the tree hydrates them, making them usable as nodes.
                return rows;
            }
            setCell({ key, cell, }) {
                const { column: columnOrId, row: rowOrId } = key;
                const row = __classPrivateFieldGet(this, _Table_instances, "m", _Table_getRow).call(this, rowOrId);
                const column = __classPrivateFieldGet(this, _Table_instances, "m", _Table_getColumn).call(this, columnOrId);
                __classPrivateFieldGet(this, _Table_instances, "m", _Table_applyEditsInBatch).call(this, {
                    applyEdits: () => {
                        row.cells[column.id] = cell;
                    },
                    // Relevant invariant: each cell corresponds to an existing row and column
                    // Prevents cell leaks from concurrently removed columns in earlier-sequenced edits.
                    // Example scenario: Client A removes a column, then Client B concurrently sets a cell in that column (sequenced after A's edit).
                    // If both edits are applied, B's cell would not correspond to an existing column.
                    preconditions: [
                        {
                            type: "nodeInDocument",
                            node: column,
                        },
                    ],
                    // Relevant invariant: each cell corresponds to an existing row and column
                    // Example scenario: Client A overwrites a populated cell, then Client B removes the column associated with the A's cell (this clears the cell).
                    // If Client A then reverts their edit, the overwritten value is restored in the cell despite the absence of column for that cell.
                    preconditionsOnRevert: row.cells[column.id] === undefined
                        ? undefined
                        : [
                            {
                                type: "nodeInDocument",
                                node: column,
                            },
                        ],
                });
            }
            removeColumns(indexOrColumns, count = undefined) {
                // Relevant invariant: each cell corresponds to an existing row and column
                // Prevents cell leaks from concurrently added rows in earlier-sequenced edits.
                // Example scenario: Client A adds a row, Client B concurrently removes a column that is populated in A's row.
                // If Client A's edit is sequenced before Client B's edit, then B's removal would orphan the cell in A's row.
                // We have the same problem if Client A populates a cell for one of the columns removed by B
                // This constraint ensures no rows were added.
                // TODO: Replace with "no attach" constraint on the row array when available.
                const preconditions = [{ type: "noChange" }];
                if (typeof indexOrColumns === "number" || indexOrColumns === undefined) {
                    let removedColumns;
                    const startIndex = indexOrColumns ?? 0;
                    const endIndex = count === undefined ? this.table.columns.length : startIndex + count;
                    // If there are no columns to remove, do nothing
                    if (startIndex === endIndex) {
                        return [];
                    }
                    (0, index_js_4.validateIndexRange)(startIndex, endIndex, this.table.columns, "Table.removeColumns");
                    __classPrivateFieldGet(this, _Table_instances, "m", _Table_applyEditsInBatch).call(this, {
                        applyEdits: () => {
                            const columnsToRemove = this.table.columns.slice(startIndex, endIndex);
                            // First, remove all cells that correspond to each column from each row:
                            for (const column of columnsToRemove) {
                                for (const row of this.table.rows) {
                                    // TypeScript is unable to narrow the row type correctly here, hence the cast.
                                    // See: https://github.com/microsoft/TypeScript/issues/52144
                                    // eslint-disable-next-line @typescript-eslint/no-dynamic-delete -- This is currently how Record node entries are deleted.
                                    delete row.cells[column.id];
                                }
                            }
                            // Second, remove the column nodes:
                            removeRangeFromArray(startIndex, endIndex, this.table.columns, "Table.removeColumns");
                            removedColumns = columnsToRemove;
                        },
                        preconditions,
                    });
                    return removedColumns ?? (0, internal_1.fail)(0xc1f /* Transaction did not complete. */);
                }
                else {
                    // If there are no columns to remove, do nothing
                    if (indexOrColumns.length === 0) {
                        return [];
                    }
                    // Resolve any IDs to actual nodes.
                    // This validates that all of the rows exist before starting transaction.
                    // This improves user-facing error experience.
                    const columnsToRemove = [];
                    for (const columnOrIdToRemove of indexOrColumns) {
                        columnsToRemove.push(__classPrivateFieldGet(this, _Table_instances, "m", _Table_getColumn).call(this, columnOrIdToRemove));
                    }
                    __classPrivateFieldGet(this, _Table_instances, "m", _Table_applyEditsInBatch).call(this, {
                        applyEdits: () => {
                            // Note, throwing an error within a transaction will abort the entire transaction.
                            // So if we throw an error here for any column, no columns will be removed.
                            for (const columnToRemove of columnsToRemove) {
                                // Remove the corresponding cell from all rows.
                                for (const row of this.table.rows) {
                                    // TypeScript is unable to narrow the row type correctly here, hence the cast.
                                    // See: https://github.com/microsoft/TypeScript/issues/52144
                                    // eslint-disable-next-line @typescript-eslint/no-dynamic-delete -- This is currently how Record node entries are deleted.
                                    delete row.cells[columnToRemove.id];
                                }
                                // We have already validated that all of the columns exist above, so this is safe.
                                this.table.columns.removeAt(this.table.columns.indexOf(columnToRemove));
                            }
                        },
                        preconditions,
                    });
                    return columnsToRemove;
                }
            }
            removeRows(indexOrRows, count) {
                // Relevant invariant: each cell corresponds to an existing row and column
                // Adding a constraint on columns here to prevent cells being orphaned.  The relevant scenario is:
                // Client A removes rows
                // Client B (either concurrently or not, so long as B's edit is sequenced after A's edit) removes a column,
                // Client A reverts the removal of the rows
                // TODO: Replace with "no detach on revert" constraint on the column array when available.
                const columnConstraints = this.table.columns.map((column) => ({
                    type: "nodeInDocument",
                    node: column,
                }));
                if (typeof indexOrRows === "number" || indexOrRows === undefined) {
                    let removedRows;
                    const startIndex = indexOrRows ?? 0;
                    const endIndex = count === undefined ? this.table.rows.length : startIndex + count;
                    // If there are no rows to remove, do nothing
                    if (startIndex === endIndex) {
                        return [];
                    }
                    (0, index_js_4.validateIndexRange)(startIndex, endIndex, this.table.rows, "Table.removeRows");
                    __classPrivateFieldGet(this, _Table_instances, "m", _Table_applyEditsInBatch).call(this, {
                        applyEdits: () => {
                            removedRows = removeRangeFromArray(startIndex, endIndex, this.table.rows, "Table.removeRows");
                        },
                        preconditionsOnRevert: columnConstraints.length > 0 ? columnConstraints : undefined,
                    });
                    return removedRows ?? (0, internal_1.fail)(0xccd /* Transaction did not complete */);
                }
                // If there are no rows to remove, do nothing
                if (indexOrRows.length === 0) {
                    return [];
                }
                // Resolve any IDs to actual nodes.
                // This validates that all of the rows exist before starting transaction.
                // This improves user-facing error experience.
                const rowsToRemove = [];
                for (const rowToRemove of indexOrRows) {
                    rowsToRemove.push(__classPrivateFieldGet(this, _Table_instances, "m", _Table_getRow).call(this, rowToRemove));
                }
                __classPrivateFieldGet(this, _Table_instances, "m", _Table_applyEditsInBatch).call(this, {
                    applyEdits: () => {
                        // Note, throwing an error within a transaction will abort the entire transaction.
                        // So if we throw an error here for any row, no rows will be removed.
                        for (const rowToRemove of rowsToRemove) {
                            // We have already validated that all of the rows exist above, so this is safe.
                            const index = this.table.rows.indexOf(rowToRemove);
                            this.table.rows.removeAt(index);
                        }
                    },
                    preconditionsOnRevert: columnConstraints.length > 0 ? columnConstraints : undefined,
                });
                return rowsToRemove;
            }
            removeCell(key) {
                const { column: columnOrIdOrIndex, row: rowOrIdOrIndex } = key;
                const row = __classPrivateFieldGet(this, _Table_instances, "m", _Table_getRow).call(this, rowOrIdOrIndex);
                const column = __classPrivateFieldGet(this, _Table_instances, "m", _Table_getColumn).call(this, columnOrIdOrIndex);
                const cell = row.cells[column.id];
                if (cell === undefined) {
                    return undefined;
                }
                __classPrivateFieldGet(this, _Table_instances, "m", _Table_applyEditsInBatch).call(this, {
                    applyEdits: () => {
                        // eslint-disable-next-line @typescript-eslint/no-dynamic-delete -- This is currently how Record node entries are deleted.
                        delete row.cells[column.id];
                    },
                    // Relevant invariant: each cell corresponds to an existing row and column
                    // Prevents cell leaks from concurrently removed columns in earlier-sequenced edits when this cell removal is reverted.
                    // Example scenario: Client A removes a cell, then Client B removes the column for that cell.
                    // If A's cell removal is later reverted, the cell would be restored but B's column removal means there's no column for it.
                    // This constraint on revert ensures the column still exists, ensuring restored cells correspond to existing columns.
                    preconditionsOnRevert: [{ type: "nodeInDocument", node: column }],
                });
                return cell;
            }
            /**
             * Throw a `UsageError` for a missing Column by its ID or index.
             */
            static _throwMissingColumnError(columnOrIdOrIndex) {
                if (typeof columnOrIdOrIndex === "number") {
                    throw new internal_2.UsageError(`No column exists at index ${columnOrIdOrIndex}.`);
                }
                if (typeof columnOrIdOrIndex === "string") {
                    throw new internal_2.UsageError(`No column with ID "${columnOrIdOrIndex}" exists in the table.`);
                }
                throw new internal_2.UsageError(`The specified column node with ID "${columnOrIdOrIndex.id}" does not exist in the table.`);
            }
            /**
             * Validates the provided list of new columns being inserted into the table.
             * @throws Throws a `UsageError` if any of the following conditions are met:
             * - A column with a duplicate ID is being inserted.
             */
            static _validateNewColumns(newColumns, existingColumnIds) {
                const newColumnIds = new Set();
                for (const newColumn of newColumns) {
                    // #region Ensure each column ID is unique
                    const newColumnId = newColumn.id;
                    if (existingColumnIds.has(newColumnId)) {
                        throw new internal_2.UsageError(`Attempted to insert a column with ID "${newColumnId}", but a column with that ID already exists in the table.`);
                    }
                    if (newColumnIds.has(newColumnId)) {
                        throw new internal_2.UsageError(`Attempted to insert multiple columns with ID "${newColumnId}". Column IDs must be unique.`);
                    }
                    newColumnIds.add(newColumnId);
                }
            }
            /**
             * Validates the provided list of new rows being inserted into the table.
             * @throws Throws a `UsageError` if any of the following conditions are met:
             * - A row with a duplicate ID is being inserted.
             * - A row is being inserted that contains cells for columns that do not exist in the table.
             */
            static _validateNewRows(newRows, existingRowIds, columnIds) {
                const newRowIds = new Set();
                for (const newRow of newRows) {
                    // #region Ensure each row ID is unique
                    const newRowId = newRow.id;
                    if (existingRowIds.has(newRowId)) {
                        throw new internal_2.UsageError(`Attempted to insert a row with ID "${newRowId}", but a row with that ID already exists in the table.`);
                    }
                    if (newRowIds.has(newRowId)) {
                        throw new internal_2.UsageError(`Attempted to insert multiple rows with ID "${newRowId}". Row IDs must be unique.`);
                    }
                    newRowIds.add(newRowId);
                    // #endregion
                    // #region If the row contains cells, verify that the table contains the columns for those cells
                    // Note: we intentionally hide `cells` on `IRow` to avoid leaking the internal data representation as much as possible, so we have to cast here.
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
                    if (newRow.cells !== undefined) {
                        // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
                        const keys = Object.keys(newRow.cells);
                        for (const key of keys) {
                            if (!columnIds.has(key)) {
                                throw new internal_2.UsageError(`Attempted to insert a row containing a cell under column ID "${key}", but the table does not contain a column with that ID.`);
                            }
                        }
                    }
                    // #endregion
                }
            }
            /**
             * Throw a `UsageError` for a missing Row by its ID or index.
             */
            static _throwMissingRowError(rowOrIdOrIndex) {
                if (typeof rowOrIdOrIndex === "number") {
                    throw new internal_2.UsageError(`No row exists at index ${rowOrIdOrIndex}.`);
                }
                if (typeof rowOrIdOrIndex === "string") {
                    throw new internal_2.UsageError(`No row with ID "${rowOrIdOrIndex}" exists in the table.`);
                }
                throw new internal_2.UsageError(`The specified row node with ID "${rowOrIdOrIndex.id}" does not exist in the table.`);
            }
        }
        _a = Table, _Table_instances = new WeakSet(), _Table_applyEditsInBatch = function _Table_applyEditsInBatch(options) {
            const { applyEdits, preconditions, preconditionsOnRevert } = options;
            const branch = index_js_2.TreeAlpha.branch(this);
            // Ensure events are paused until all of the edits are applied.
            // This ensures that the user sees the corresponding table-level edit as atomic,
            // and ensures they are not spammed with intermediate events.
            (0, index_js_3.withBufferedTreeEvents)(() => {
                if (branch === undefined) {
                    // If this node does not have a corresponding branch, then it is unhydrated.
                    // I.e., it is not part of a collaborative session yet.
                    // Therefore, we don't need to run the edits as a transaction.
                    applyEdits();
                }
                else {
                    branch.runTransaction(() => {
                        applyEdits();
                        if (preconditionsOnRevert !== undefined) {
                            return { preconditionsOnRevert };
                        }
                    }, preconditions === undefined ? undefined : { preconditions });
                }
            });
        }, _Table_tryGetColumn = function _Table_tryGetColumn(columnOrIdOrIndex) {
            if (typeof columnOrIdOrIndex === "number") {
                if (columnOrIdOrIndex < 0 || columnOrIdOrIndex >= this.table.columns.length) {
                    return undefined;
                }
                // TypeScript is unable to narrow the types correctly here, hence the cast.
                // See: https://github.com/microsoft/TypeScript/issues/52144
                return this.table.columns[columnOrIdOrIndex];
            }
            if (typeof columnOrIdOrIndex === "string") {
                const columnId = columnOrIdOrIndex;
                // TypeScript is unable to narrow the types correctly here, hence the casts.
                // See: https://github.com/microsoft/TypeScript/issues/52144
                return this.table.columns.find((col) => col.id === columnId);
            }
            // If the user provided a node, ensure it actually exists in this table.
            if (!this.table.columns.includes(columnOrIdOrIndex)) {
                return undefined;
            }
            return columnOrIdOrIndex;
        }, _Table_getColumn = function _Table_getColumn(columnOrIdOrIndex) {
            const column = __classPrivateFieldGet(this, _Table_instances, "m", _Table_tryGetColumn).call(this, columnOrIdOrIndex);
            if (column === undefined) {
                _a._throwMissingColumnError(columnOrIdOrIndex);
            }
            return column;
        }, _Table_tryGetRow = function _Table_tryGetRow(rowOrIdOrIndex) {
            if (typeof rowOrIdOrIndex === "number") {
                if (rowOrIdOrIndex < 0 || rowOrIdOrIndex >= this.table.rows.length) {
                    return undefined;
                }
                // TypeScript is unable to narrow the types correctly here, hence the cast.
                // See: https://github.com/microsoft/TypeScript/issues/52144
                return this.table.rows[rowOrIdOrIndex];
            }
            if (typeof rowOrIdOrIndex === "string") {
                const rowId = rowOrIdOrIndex;
                // TypeScript is unable to narrow the types correctly here, hence the casts.
                // See: https://github.com/microsoft/TypeScript/issues/52144
                return this.table.rows.find((row) => row.id === rowId);
            }
            // If the user provided a node, ensure it actually exists in this table.
            if (!this.table.rows.includes(rowOrIdOrIndex)) {
                return undefined;
            }
            return rowOrIdOrIndex;
        }, _Table_getRow = function _Table_getRow(rowOrIdOrIndex) {
            const row = __classPrivateFieldGet(this, _Table_instances, "m", _Table_tryGetRow).call(this, rowOrIdOrIndex);
            if (row === undefined) {
                _a._throwMissingRowError(rowOrIdOrIndex);
            }
            return row;
        }, _Table_validateNewColumns = function _Table_validateNewColumns(newColumns) {
            return _a._validateNewColumns(newColumns, new Set(this.table.columns.map((column) => column.id)));
        }, _Table_validateNewRows = function _Table_validateNewRows(newRows) {
            return _a._validateNewRows(newRows, new Set(this.table.rows.map((row) => row.id)), new Set(this.table.columns.map((column) => column.id)));
        };
        // Returning SingletonSchema without a type conversion results in TypeScript generating something like `readonly "__#124291@#brand": unknown;`
        // for the private brand field of TreeNode.
        // This numeric id doesn't seem to be stable over incremental builds, and thus causes diffs in the API extractor reports.
        // This is avoided by doing this type conversion.
        // The conversion is done via eraseSubclassableSchemaDetails to erase internal details.
        const TableSchemaType = (0, index_js_3.eraseSchemaDetailsSubclassable)()(Table);
        // Return the table schema
        return TableSchemaType;
    }
    System_TableSchema.createTableSchema = createTableSchema;
    // #endregion
})(System_TableSchema || (exports.System_TableSchema = System_TableSchema = {}));
/**
 * Sets up scope for table schema built-in types.
 * @remarks User-provided factory scoping will be applied as `com.fluidframework.table<user-scope>`.
 */
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function createTableScopedFactory(inputSchemaFactory) {
    return (0, index_js_3.createCustomizedFluidFrameworkScopedFactory)(inputSchemaFactory, "tableV2");
}
/**
 * Removes the specified range of elements from the array.
 * @returns The removed elements.
 */
function removeRangeFromArray(startIndex, endIndex, array, methodName) {
    (0, index_js_4.validateIndexRange)(startIndex, endIndex, array, methodName);
    // TypeScript is unable to narrow the array element type correctly here, hence the cast.
    // See: https://github.com/microsoft/TypeScript/issues/52144
    const removedRows = array.slice(startIndex, endIndex);
    array.removeRange(startIndex, endIndex);
    return removedRows;
}
/**
 * Contains types and factories for creating schema to represent dynamic tabular data.
 *
 * @remarks
 *
 * Note: the APIs produced by this module ensure various tabular data invariants are maintained that the raw, underlying tree structures do not.
 * For example, they ensure that cells always correspond to existing rows and columns (and do not become "orphaned" due to row/column deletion, etc.).
 * For this reason, direct manipulation of the underlying tree structures is not supported.
 * To modify the data, only the APIs provided here may be used.
 *
 * Also note: these APIs leverage `SharedTree` functionality that was added in version `2.80.0`,
 * which is not compatible with previous versions of this library.
 * To ensure safe collaboration, you will need to configure the {@link @fluidframework/runtime-definitions#MinimumVersionForCollab}
 * for the Fluid Runtime and/or `SharedTree` to at least `2.80.0`.
 * To set this minimum version for `SharedTree`, use {@link configuredSharedTreeBeta}.
 *
 * The primary APIs for create tabular data schema are:
 *
 * - {@link TableSchema.(table:1)}
 *
 * - {@link TableSchema.(column:1)}
 *
 * - {@link TableSchema.(row:1)}
 *
 * Tables created using these APIs are...
 *
 * - sparse, meaning that cells may be omitted, and new rows are empty by default.
 *
 * - dynamic, meaning that their structure can be modified at runtime.
 * Columns and rows can be inserted, removed, modified, and reordered.
 * Cells can be inserted, removed, and modified.
 *
 * - row-major, meaning that operating on rows (including inserts, removal, moves, and traversal) is more efficient than operating on columns.
 *
 * Column and Row schema created using these APIs are extensible via the `props` field.
 * This allows association of additional properties with column and row nodes.
 *
 * @example Defining a Table schema
 *
 * ```typescript
 * class MyTable extends TableSchema.table({
 * 	schemaFactory,
 * 	cell: schemaFactory.string,
 * }) {}
 *
 * const table = MyTable.create({
 * 	columns: [{ id: "column-0" }],
 * 	rows: [{ id: "row-0", cells: { "column-0": "Hello world!" } }],
 * });
 * ```
 *
 * @example Customizing Column and Row schema
 *
 * ```typescript
 * const Cell = schemaFactory.string;
 *
 * class MyColumn extends TableSchema.column({
 * 	schemaFactory,
 * 	cell: Cell,
 * 	props: schemaFactory.object("TableColumnProps", {
 * 		label: schemaFactory.string,
 * 	}),
 * }) {}
 *
 * class MyRow extends TableSchema.row({
 * 	schemaFactory,
 * 	cell: Cell,
 * }) {}
 *
 * class MyTable extends TableSchema.table({
 * 	schemaFactory,
 * 	cell: Cell,
 * 	column: MyColumn,
 * 	row: MyRow,
 * }) {}
 *
 * const table = new MyTable({
 * 	columns: [
 * 		new MyColumn({ props: { label: "Entry" } }),
 * 		new MyColumn({ props: { label: "Date" } }),
 * 		new MyColumn({ props: { label: "Amount" } }),
 * 	],
 * 	rows: [],
 * });
 * ```
 *
 * @example Listening for changes in the table
 *
 * ```typescript
 * // Listen for any changes to the table and its children.
 * // The "treeChanged" event will fire when the associated node or any of its descendants change.
 * Tree.on(table, "treeChanged", () => {
 * 	// Respond to the change.
 * });
 * ```
 *
 * @example Listening for changes to the rows list only
 *
 * ```typescript
 * // Listen for any changes to the list of rows.
 * // The "nodeChanged" event will fire only when the specified node itself changes (i.e., its own properties change).
 * // In this case, the event will fire when a row is added or removed, or the order of the list is changed.
 * // But it won't fire when a row's properties change, or when the row's cells change, etc.
 * Tree.on(table.rows, "nodeChanged", () => {
 * 	// Respond to the change.
 * });
 * ```
 *
 * @privateRemarks
 * The above examples are backed by tests in `tableSchema.spec.ts`.
 * Those tests and these examples should be kept in-sync to ensure that the examples are correct.
 *
 * @beta
 */
var TableSchema;
(function (TableSchema) {
    // #region Column
    /**
     * Overload implementation
     */
    function column({ schemaFactory, cell, props = index_js_3.SchemaFactory.optional(index_js_3.SchemaFactory.null), }) {
        return System_TableSchema.createColumnSchema(schemaFactory, props);
    }
    TableSchema.column = column;
    /**
     * Overload implementation
     */
    function row({ schemaFactory, cell, props = index_js_3.SchemaFactory.optional(index_js_3.SchemaFactory.null), }) {
        return System_TableSchema.createRowSchema(schemaFactory, cell, props);
    }
    TableSchema.row = row;
    /**
     * Overload implementation
     */
    function table({ schemaFactory, cell: cellSchema, column: columnSchema = column({
        schemaFactory,
        cell: cellSchema,
    }), row: rowSchema = row({
        schemaFactory,
        cell: cellSchema,
    }), }) {
        return System_TableSchema.createTableSchema(schemaFactory, cellSchema, columnSchema, rowSchema);
    }
    TableSchema.table = table;
    // #endregion
})(TableSchema || (exports.TableSchema = TableSchema = {}));
//# sourceMappingURL=tableSchema.js.map