"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getCompatibility = exports.SnapshotCompatibilityChecker = exports.snapshotSchemaCompatibility = exports.importCompatibilitySchemaSnapshot = exports.exportCompatibilitySchemaSnapshot = exports.checkCompatibility = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const internal_2 = require("@fluidframework/runtime-utils/internal");
const internal_3 = require("@fluidframework/telemetry-utils/internal");
const semver = __importStar(require("semver-ts"));
const toStoredSchema_js_1 = require("../toStoredSchema.js");
const treeSchema_js_1 = require("../treeSchema.js");
const configuration_js_1 = require("./configuration.js");
const schemaCompatibilityTester_js_1 = require("./schemaCompatibilityTester.js");
const schemaFromSimple_js_1 = require("./schemaFromSimple.js");
const simpleSchemaCodec_js_1 = require("./simpleSchemaCodec.js");
/**
 * Compute the compatibility of using `view` to {@link ViewableTree.viewWith | view a tree} who's {@link ITreeAlpha.exportSimpleSchema | stored schema} could be derived from `viewWhichCreatedStoredSchema` via either {@link TreeView.initialize} or {@link TreeView.upgradeSchema}.
 *
 * @remarks See {@link SchemaCompatibilityStatus} for details on the compatibility results.
 *
 * @example This example demonstrates checking the compatibility of a historical schema against a current schema.
 * In this case, the historical schema is a Point2D object with x and y fields, while the current schema is a Point3D object
 * that adds an optional z field.
 *
 * ```ts
 * // This snapshot is assumed to be the same as Point3D, except missing `z`.
 * const encodedSchema = JSON.parse(fs.readFileSync("PointSchema.json", "utf8"));
 * const oldViewSchema = importCompatibilitySchemaSnapshot(encodedSchema);
 *
 * // Build the current view schema
 * class Point3D extends factory.object("Point", {
 * 	x: factory.number,
 * 	y: factory.number,
 *
 * 	// The current schema has a new optional field that was not present on Point2D
 * 	z: factory.optional(factory.number),
 * }) {}
 * const currentViewSchema = new TreeViewConfiguration({ schema: Point3D });
 *
 * // Check to see if the document created by the historical view schema can be opened with the current view schema
 * const backwardsCompatibilityStatus = checkCompatibility(oldViewSchema, currentViewSchema);
 *
 * // z is not present in Point2D, so the schema must be upgraded
 * assert.equal(backwardsCompatibilityStatus.canView, false);
 *
 * // The schema can be upgraded to add the new optional field
 * assert.equal(backwardsCompatibilityStatus.canUpgrade, true);
 *
 * // Test what the old version of the application would do with a tree using the new schema:
 * const forwardsCompatibilityStatus = checkCompatibility(currentViewSchema, oldViewSchema);
 *
 * // If the old schema set allowUnknownOptionalFields, this would be true, but since it did not,
 * // this assert will fail, detecting the forwards compatibility break:
 * // this means these two versions of the application cannot collaborate on content using these schema.
 * assert.equal(forwardsCompatibilityStatus.canView, true);
 * ```
 *
 * @param viewWhichCreatedStoredSchema - From which to derive the stored schema, as if it initialized or upgraded a tree via {@link TreeView}.
 * @param view - The view being tested to see if it could view tree created or initialized using `viewWhichCreatedStoredSchema`.
 * @returns The compatibility status.
 *
 * @privateRemarks
 * TODO: a simple high level API for snapshot based schema compatibility checking should replace the need to export this.
 *
 * @alpha
 */
function checkCompatibility(viewWhichCreatedStoredSchema, view) {
    const viewAsAlpha = new configuration_js_1.TreeViewConfigurationAlpha({ schema: view.schema });
    const stored = (0, toStoredSchema_js_1.toInitialSchema)(viewWhichCreatedStoredSchema.schema);
    const tester = new schemaCompatibilityTester_js_1.SchemaCompatibilityTester(viewAsAlpha);
    return tester.checkCompatibility(stored);
}
exports.checkCompatibility = checkCompatibility;
/**
 * Returns a JSON compatible representation of the tree schema for snapshot compatibility checking.
 *
 * Snapshots can be loaded by the same or newer package versions, but not necessarily older versions.
 *
 * @see {@link importCompatibilitySchemaSnapshot} which loads these snapshots.
 *
 * @param config - The schema to snapshot. Only the schema field of the `TreeViewConfiguration` is used.
 * @returns The JSON representation of the schema.
 *
 * @example This example creates and persists a snapshot of a Point2D schema.
 *
 * ```ts
 * const schemaFactory = new SchemaFactory("test");
 * class Point2D extends schemaFactory.object("Point", {
 * 	x: factory.number,
 * 	y: factory.number,
 * }) {}
 * const viewSchema = new TreeViewConfiguration({ schema: Point2D });
 * const encodedSchema = JSON.stringify(exportCompatibilitySchemaSnapshot(viewSchema));
 * fs.writeFileSync("PointSchema.json", encodedSchema);
 * ```
 *
 * @privateRemarks
 * TODO: a simple high level API for snapshot based schema compatibility checking should replace the need to export this.
 *
 * @alpha
 */
function exportCompatibilitySchemaSnapshot(config) {
    const treeSchema = (0, treeSchema_js_1.createTreeSchema)(config.schema);
    return (0, simpleSchemaCodec_js_1.encodeSchemaCompatibilitySnapshot)(treeSchema);
}
exports.exportCompatibilitySchemaSnapshot = exportCompatibilitySchemaSnapshot;
/**
 * Parse the format exported by {@link exportCompatibilitySchemaSnapshot} into a schema.
 *
 * Can load snapshots created by the same or older package versions, but not necessarily newer versions.
 *
 * @see {@link exportCompatibilitySchemaSnapshot} which creates these snapshots.
 *
 * @param config - The JSON representation of the schema.
 * @returns The schema. Only the schema field of the {@link TreeViewConfiguration} is populated.
 * @throws Will throw a usage error if the encoded schema is not in the expected format.
 *
 * @example This example loads and parses a snapshot of a Point2D schema.
 *
 * ```ts;
 * const oldViewSchema = importCompatibilitySchemaSnapshot(fs.readFileSync("PointSchema.json", "utf8"));
 * ```
 * @privateRemarks
 * TODO: a simple high level API for snapshot based schema compatibility checking should replace the need to export this.
 * @alpha
 */
function importCompatibilitySchemaSnapshot(config) {
    const simpleSchema = (0, simpleSchemaCodec_js_1.decodeSchemaCompatibilitySnapshot)(config);
    const viewSchema = (0, schemaFromSimple_js_1.generateSchemaFromSimpleSchema)(simpleSchema);
    // We construct a TreeViewConfiguration here with the default parameters. The default set of validation parameters are fine for
    // a schema produced by `generateSchemaFromSimpleSchema`.
    return new configuration_js_1.TreeViewConfiguration({ schema: viewSchema.root });
}
exports.importCompatibilitySchemaSnapshot = importCompatibilitySchemaSnapshot;
/**
 * Check `currentViewSchema` for compatibility with a collection of historical schema snapshots stored in `snapshotDirectory`.
 *
 * @throws Throws errors if the input version strings (including those in snapshot file names) are not valid semver versions when using default semver version comparison.
 * @throws Throws errors if the input version strings (including those in snapshot file names) are not ordered as expected (current being the highest, and `minVersionForCollaboration` corresponding to the current version or a lower snapshotted version).
 * @throws In `test` mode, throws an error if there is not an up to date snapshot for the current version.
 * @throws Throws an error if any snapshotted schema cannot be upgraded to the current schema.
 * @throws Throws an error if any snapshotted schema with a version greater than or equal to `minVersionForCollaboration` cannot view documents created with the current schema.
 * @remarks
 * This is intended for use in snapshot-based schema compatibility tests.
 * Every SharedTree-based component or application with a schema is recommended to use this to verify schema compatibility across versions.
 *
 * Schema snapshots should be added to `snapshotDirectory` using this function in "update" mode whenever the schema changes in a compatibility impacting way
 * (or when `snapshotUnchangedVersions` is true and a new version about to be released is getting prepared for release (and thus `version` changed)).
 *
 * This will throw an exception if any snapshotted schema would result in documents that cannot be viewed (after using {@link TreeView.upgradeSchema}), or if any schema with a version greater than or equal to `minVersionForCollaboration` cannot view documents created with the `currentViewSchema`.
 * See {@link TreeView.compatibility} for more information.
 *
 * Proper use of this utility should do a good job at detecting schema compatibility issues,
 * however it currently does not do a good job of explaining exactly what change to the schema is causing the compatibility issues.
 * This is a known limitation that will be improved in future releases.
 * These improvements, as well as other changes, may change the exact messages produced by this function in the error cases: no stability of these messages should be assumed.
 *
 * Unlike some other snapshot based testing tools, this stores more than just the current snapshot: historical snapshots are retained as well.
 * Retention of these additional historical snapshots, whose content can't be regenerated from the current schema, is necessary to properly test compatibility across versions.
 * Since there is content in the snapshots which cannot be regenerated, tools which assume all snapshotted content can be regenerated cannot be used here.
 * This means that tools like Jest's built in snapshot testing are not suitable for this purpose.
 * These snapshots behave partly like test data, and partly like snapshots.
 * Typically the easiest way to manage this is to place {@link SnapshotSchemaCompatibilityOptions.snapshotDirectory} inside a directory appropriate for test data,
 * and use node to provide the filesystem access via {@link SnapshotSchemaCompatibilityOptions.fileSystem}.
 *
 * For now, locating what change broke compatibility is likely best discovered by making small schema changes one at a time and updating the snapshot and reviewing the diffs.
 * Details for what kinds of changes are breaking and in which ways can be found in the documentation for {@link TreeView.compatibility} and
 * {@link https://fluidframework.com/docs/data-structures/tree/schema-evolution/ | schema-evolution}.
 *
 * This utility does not enforce anything with respect to API compatibility, or special semantics for major, minor, or patch versions.
 * Libraries which export schema for use by others will need to take special care to ensure the stability contract they offer their users aligns which what is validated by this utility.
 *
 * This utility only tests compatibility of the historical snapshots against the current schema; it does not test them against each-other.
 * Generally any historical schema should have been tested against the ones before them at the time they were current.
 * If for some reason a version of a schema made it into production that was not compatible with a previous version,
 * that can still be represented here (but may require manually generating a snapshot for that version)
 * and this will still allow testing that all historical version can be upgraded to the current one.
 * If a sufficiently incompatible historical schema were used in production, it may be impossible to make a single schema which can accommodate all of them:
 * this utility can be used to confirm that is the case, as well as to avoid the problem in the first place by testing schema before each one is deployed.
 *
 * @example Mocha test which validates the current `config` can collaborate with all historical version back to 2.0.0, and load and update any versions older than that.
 * ```typescript
 * it("schema compatibility", () => {
 * 	snapshotSchemaCompatibility({
 * 		version: pkgVersion,
 * 		schema: config,
 * 		fileSystem: { ...fs, ...path },
 * 		minVersionForCollaboration: "2.0.0",
 * 		mode: process.argv.includes("--snapshot") ? "update" : "test",
 * 		snapshotDirectory,
 * 	});
 * });
 * ```
 * @example Complete Mocha test file
 * ```typescript
 * import fs from "node:fs";
 * import path from "node:path";
 *
 * import { snapshotSchemaCompatibility } from "@fluidframework/tree/beta";
 *
 * // The TreeViewConfiguration the application uses, which contains the application's schema.
 * import { treeViewConfiguration } from "./schema.js";
 * // The next version of the application which will be released.
 * import { packageVersion } from "./version.js";
 *
 * // Provide some way to run the check in "update" mode when updating snapshots is intended.
 * const regenerateSnapshots = process.argv.includes("--snapshot");
 *
 * // Setup the actual test. In this case using Mocha syntax.
 * describe("schema", () => {
 * 	it("schema compatibility", () => {
 * 		// Select a path to save the snapshots in.
 * 		// This will depend on how your application organizes its test data.
 * 		const snapshotDirectory = path.join(
 * 			import.meta.dirname,
 * 			"../../../src/test/schema-snapshots",
 * 		);
 * 		snapshotSchemaCompatibility({
 * 			schema: config,
 * 			fileSystem: { ...fs, ...path },
 * 			version: pkgVersion,
 * 			minVersionForCollaboration: "2.0.0",
 * 			mode: process.argv.includes("--snapshot") ? "update" : "assert",
 * 			snapshotDirectory,
 * 		});
 * 	});
 * });
 * ```
 * @privateRemarks
 * Use of this function within this package (for schema libraries released as part of this package) should use {@link testSchemaCompatibilitySnapshots} instead.
 *
 * This uses the format defined in simpleSchemaCodec.ts.
 * This does include versioning information in the snapshot format,
 * but it would be nice to better unify how we do that versioning and format validation with our codecs.
 *
 * See snapshotCompatibilityChecker.example.mts for the large example included above.
 * @beta
 */
function snapshotSchemaCompatibility(options) {
    const checker = new SnapshotCompatibilityChecker(options.snapshotDirectory, options.fileSystem);
    const { version: currentVersion, schema: currentViewSchema, mode, minVersionForCollaboration, snapshotUnchangedVersions, rejectVersionsWithNoSchemaChange, rejectSchemaChangesWithNoVersionChange, } = options;
    const validateVersion = options.versionComparer === undefined ? semver.valid : (v) => v;
    const versionComparer = options.versionComparer ?? semver.compare;
    if (validateVersion(currentVersion) === null) {
        throw new internal_3.UsageError(`Invalid version: ${JSON.stringify(currentVersion)}. Must be a valid semver version.`);
    }
    if (validateVersion(minVersionForCollaboration) === null) {
        throw new internal_3.UsageError(`Invalid minVersionForCollaboration: ${JSON.stringify(minVersionForCollaboration)}. Must be a valid semver version.`);
    }
    if (versionComparer(minVersionForCollaboration, currentVersion) > 0) {
        throw new internal_3.UsageError(`Invalid minVersionForCollaboration: ${JSON.stringify(minVersionForCollaboration)}. Must be less than or equal to current version ${JSON.stringify(currentVersion)}.`);
    }
    if (mode !== "assert" && mode !== "update") {
        throw new internal_3.UsageError(`Invalid mode: ${JSON.stringify(mode)}. Must be either "assert" or "update".`);
    }
    const currentEncodedForSnapshotting = exportCompatibilitySchemaSnapshot(currentViewSchema);
    const snapshots = checker.readAllSchemaSnapshots(versionComparer);
    const compatibilityErrors = [];
    const contextNotes = [];
    function errorWithContext(message) {
        return new Error([
            "Schema compatibility check failed:",
            message,
            `Snapshots in: ${JSON.stringify(options.snapshotDirectory)}`,
            `Snapshots exist for versions: ${JSON.stringify([...snapshots.keys()], undefined, "\t")}.`,
            ...contextNotes,
        ].join("\n"));
    }
    const compatibilityMap = (0, internal_1.transformMapValues)(snapshots, (snapshot) => getCompatibility(currentViewSchema, snapshot));
    // Either:
    // - false: no update needed
    // - the updateError message (update in update mode, error otherwise)
    // - an error if the update is disallowed by the flags
    let wouldUpdate;
    // Set wouldUpdate
    {
        const latestSnapshot = [...snapshots][snapshots.size - 1];
        if (latestSnapshot === undefined) {
            wouldUpdate = `No snapshots found.`;
        }
        else {
            const latestCompatibility = compatibilityMap.get(latestSnapshot[0]) ??
                (0, internal_1.fail)(0xcd1 /* missing compatibilityMap entry */);
            const schemaChange = !latestCompatibility.identicalCompatibility;
            const versionChange = versionComparer(latestSnapshot[0], currentVersion) !== 0;
            if (rejectVersionsWithNoSchemaChange === true && versionChange && !schemaChange) {
                wouldUpdate = errorWithContext(`Rejecting version change (${JSON.stringify(latestSnapshot[0])} to ${JSON.stringify(currentVersion)}) due to rejectVersionsWithNoSchemaChange being set.`);
            }
            else if (rejectSchemaChangesWithNoVersionChange === true &&
                schemaChange &&
                !versionChange) {
                wouldUpdate = errorWithContext(`Rejecting schema change without version change due to existing  non-equivalent snapshot for version (${JSON.stringify(latestSnapshot[0])} due to rejectSchemaChangesWithNoVersionChange being set.`);
            }
            else if (snapshotUnchangedVersions === true) {
                const currentRead = snapshots.get(currentVersion);
                if (currentRead === undefined) {
                    wouldUpdate = `No snapshot found for version ${JSON.stringify(currentVersion)}: snapshotUnchangedVersions is true, so every version must be snapshotted.`;
                }
                else if (JSON.stringify(exportCompatibilitySchemaSnapshot(currentRead)) ===
                    JSON.stringify(currentEncodedForSnapshotting)) {
                    wouldUpdate = false;
                }
                else {
                    wouldUpdate = `Snapshot for current version ${JSON.stringify(currentVersion)} is out of date.`;
                }
            }
            else {
                if (versionComparer(latestSnapshot[0], currentVersion) <= 0) {
                    wouldUpdate = schemaChange
                        ? `Snapshot for current version ${JSON.stringify(currentVersion)} is out of date: schema has changed since latest existing snapshot version ${JSON.stringify(latestSnapshot[0])}.`
                        : false;
                }
                else {
                    wouldUpdate = errorWithContext(`Current version ${JSON.stringify(currentVersion)} is less than latest existing snapshot version ${JSON.stringify(latestSnapshot[0])}: version is expected to increase monotonically.`);
                }
            }
            if (!schemaChange && (snapshotUnchangedVersions !== true || !versionChange)) {
                // eslint-disable-next-line unicorn/no-lonely-if
                if (JSON.stringify(exportCompatibilitySchemaSnapshot(latestSnapshot[1])) !==
                    JSON.stringify(currentEncodedForSnapshotting)) {
                    // Schema are compatibility wise equivalent, but differ in some way (excluding json formatting).
                    // TODO: add a "normalize" mode, which do an update only in this case (or maybe even normalize json formatting as well and just always rewrite when !schemaChange)
                    // This would be useful to minimize diffs from future schema changes.
                    // This would be particularly useful if adding a second version of the format used in the snapshots.
                }
            }
        }
    }
    if (wouldUpdate !== false) {
        if (wouldUpdate instanceof Error) {
            throw wouldUpdate;
        }
        if (mode === "update") {
            checker.writeSchemaSnapshot(currentVersion, currentEncodedForSnapshotting);
            // Update so errors below will reflect the new snapshot.
            compatibilityMap.set(currentVersion, getCompatibility(currentViewSchema, currentViewSchema));
        }
        else {
            compatibilityErrors.push(`${wouldUpdate} If this is expected, snapshotSchemaCompatibility can be rerun in "update" mode to update or create the snapshot.`);
            // This case could update compatibilityMap as well, but it would hide some information about how the existing snapshot might be incompatible with the proposed new one.
            // This lost information could be annoying if the user's intention was not to edit the schema (which is what we assume in assert mode),
            // especially once we produce more detailed error messages that can help users understand what changed in the schema.
        }
    }
    // Add compatibilityErrors and contextNotes as needed regarding minVersionForCollaboration.
    // This is only done when minVersionForCollaboration is not the current version to avoid extra noise in "assert" mode
    // (which is the only case that could error when minVersionForCollaboration === currentVersion).
    if (minVersionForCollaboration !== currentVersion) {
        if (snapshotUnchangedVersions === true) {
            const minSnapshot = compatibilityMap.get(minVersionForCollaboration);
            if (minSnapshot === undefined) {
                compatibilityErrors.push(`Using snapshotUnchangedVersions: a snapshot of the exact minVersionForCollaboration ${JSON.stringify(minVersionForCollaboration)} is required. No snapshot found.`);
            }
        }
        else {
            const selectedMinVersionForCollaborationSnapshot = (0, internal_2.selectVersionRoundedDown)(minVersionForCollaboration, compatibilityMap, versionComparer);
            if (selectedMinVersionForCollaborationSnapshot === undefined) {
                compatibilityErrors.push(`No snapshot found with version less than or equal to minVersionForCollaboration ${JSON.stringify(minVersionForCollaboration)}.`);
            }
            else if (selectedMinVersionForCollaborationSnapshot[0] !== minVersionForCollaboration) {
                // Add an entry to ensure that the version which spans from before until after the cutoff for collaboration is included in the compatibility checks.
                compatibilityMap.set(minVersionForCollaboration, selectedMinVersionForCollaborationSnapshot[1]);
                contextNotes.push(`Due to snapshotUnchangedVersions being false and minVersionForCollaboration (${JSON.stringify(minVersionForCollaboration)}) not having an exact snapshot, the last snapshot before that version (which is ${JSON.stringify(selectedMinVersionForCollaborationSnapshot[0])}) is being also being checked as if it is version ${JSON.stringify(minVersionForCollaboration)}.`);
            }
        }
    }
    // Compare all snapshots against the current schema, using the compatibilityMap.
    for (const [snapshotVersion, compatibility] of compatibilityMap) {
        // Current should be able to view all versions.
        if (!compatibility.currentViewOfSnapshotDocument.canUpgrade) {
            compatibilityErrors.push(`Current version ${JSON.stringify(currentVersion)} cannot upgrade documents from ${JSON.stringify(snapshotVersion)}.`);
        }
        const versionComparisonToCurrent = versionComparer(snapshotVersion, currentVersion);
        if (versionComparisonToCurrent === 0) {
            if (currentVersion !== snapshotVersion) {
                throw errorWithContext(`Snapshot version ${JSON.stringify(snapshotVersion)} is semantically equal but not string equal to current version ${JSON.stringify(currentVersion)}: this is not supported.`);
            }
            if (compatibility.identicalCompatibility === false) {
                (0, internal_1.assert)(wouldUpdate !== false, 0xcd2 /* there should have been an error for the snapshot being out of date */);
            }
        }
        else if (versionComparisonToCurrent < 0) {
            // Collaboration with this version is expected to work.
            if (versionComparer(snapshotVersion, minVersionForCollaboration) >= 0) {
                // Check that the historical version can view documents from the current version, since collaboration with this one is expected to work.
                if (!compatibility.snapshotViewOfCurrentDocument.canView) {
                    compatibilityErrors.push(`Historical version ${JSON.stringify(snapshotVersion)} cannot view documents from ${JSON.stringify(currentVersion)}: these versions are expected to be able to collaborate due to the selected minVersionForCollaboration ${JSON.stringify(minVersionForCollaboration)}.`);
                }
            }
            else {
                // This is the case where the historical version is less than the minimum version for collaboration.
                // No additional validation is needed here currently, since forwards document compat from these versions is already tested above (since it applies to all snapshots).
            }
        }
        else {
            compatibilityErrors.push(`Snapshot exists for version ${JSON.stringify(snapshotVersion)} which is greater than the current version ${JSON.stringify(currentVersion)}. This is not supported.`);
        }
    }
    if (compatibilityErrors.length > 0) {
        throw errorWithContext(compatibilityErrors.map((e) => ` - ${e}`).join("\n"));
    }
}
exports.snapshotSchemaCompatibility = snapshotSchemaCompatibility;
/**
 * The high-level API for checking snapshot compatibility and generating new snapshots.
 */
class SnapshotCompatibilityChecker {
    constructor(
    /**
     * Directory where historical schema snapshots are stored.
     */
    snapshotDirectory, 
    /**
     * How the `snapshotDirectory` is accessed.
     */
    fileSystemMethods) {
        this.snapshotDirectory = snapshotDirectory;
        this.fileSystemMethods = fileSystemMethods;
    }
    writeSchemaSnapshot(snapshotName, snapshot) {
        const fullPath = this.fileSystemMethods.join(this.snapshotDirectory, `${snapshotName}.json`);
        this.ensureSnapshotDirectoryExists();
        this.fileSystemMethods.writeFileSync(fullPath, JSON.stringify(snapshot, undefined, "\t"), {
            encoding: "utf8",
        });
    }
    readSchemaSnapshot(snapshotName) {
        const snapshot = this.readSchemaSnapshotRaw(snapshotName);
        return importCompatibilitySchemaSnapshot(snapshot);
    }
    readSchemaSnapshotRaw(snapshotName) {
        const fullPath = this.fileSystemMethods.join(this.snapshotDirectory, `${snapshotName}.json`);
        const snapshot = JSON.parse(this.fileSystemMethods.readFileSync(fullPath, "utf8"));
        return snapshot;
    }
    /**
     * Returns all schema snapshots stored in the snapshot directory, sorted in order of increasing version.
     */
    readAllSchemaSnapshots(compare) {
        this.ensureSnapshotDirectoryExists();
        const files = this.fileSystemMethods.readdirSync(this.snapshotDirectory);
        const versions = [];
        for (const file of files) {
            if (file.endsWith(".json")) {
                const snapshotName = file.slice(0, ".json".length * -1);
                versions.push(snapshotName);
            }
        }
        // Ensures that errors are in a consistent and friendly order, independent of file system order.
        versions.sort(compare);
        const snapshots = new Map();
        for (const version of versions) {
            snapshots.set(version, this.readSchemaSnapshot(version));
        }
        return snapshots;
    }
    ensureSnapshotDirectoryExists() {
        this.fileSystemMethods.mkdirSync(this.snapshotDirectory, { recursive: true });
    }
}
exports.SnapshotCompatibilityChecker = SnapshotCompatibilityChecker;
/**
 * Gets the compatibility of the current view schema against a historical snapshot.
 * @param currentViewSchema - The current view schema.
 * @param previousViewSchema - The historical view schema.
 * @returns The combined compatibility status.
 */
function getCompatibility(currentViewSchema, previousViewSchema) {
    const backwardsCompatibilityStatus = checkCompatibility(previousViewSchema, currentViewSchema);
    const forwardsCompatibilityStatus = checkCompatibility(currentViewSchema, previousViewSchema);
    (0, internal_1.assert)(backwardsCompatibilityStatus.isEquivalent === forwardsCompatibilityStatus.isEquivalent, 0xcd3 /* equality should be symmetric */);
    // This relies on exportCompatibilitySchemaSnapshot being well normalized, and not differing for non-significant changes.
    const identicalCompatibility = JSON.stringify(exportCompatibilitySchemaSnapshot(currentViewSchema)) ===
        JSON.stringify(exportCompatibilitySchemaSnapshot(previousViewSchema));
    if (identicalCompatibility) {
        (0, internal_1.assert)(backwardsCompatibilityStatus.isEquivalent, 0xcd4 /* identicalCompatibility should have equivalent stored schema */);
    }
    return {
        currentViewOfSnapshotDocument: backwardsCompatibilityStatus,
        snapshotViewOfCurrentDocument: forwardsCompatibilityStatus,
        identicalCompatibility,
    };
}
exports.getCompatibility = getCompatibility;
//# sourceMappingURL=snapshotCompatibilityChecker.js.map