"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.configValueToMinVersionForCollab = exports.getValidationForRuntimeOptions = exports.validateRuntimeOptions = exports.isValidMinVersionForCollab = exports.getConfigsForMinVersionForCollab = exports.getMinVersionForCollabDefaults = exports.defaultMinVersionForCollab = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const internal_2 = require("@fluidframework/runtime-definitions/internal");
const internal_3 = require("@fluidframework/telemetry-utils/internal");
const semver_ts_1 = require("semver-ts");
const compressionDefinitions_js_1 = require("./compressionDefinitions.js");
const packageVersion_js_1 = require("./packageVersion.js");
/**
 * Our policy is to support N/N-1 compatibility by default, where N is the most
 * recent public major release of the runtime.
 * Therefore, if the customer does not provide a minVersionForCollab, we will
 * default to use N-1.
 *
 * However, this is not consistent with today's behavior. Some options (i.e.
 * batching, compression) are enabled by default despite not being compatible
 * with 1.x clients. Since the policy was introduced during 2.x's lifespan,
 * N/N-1 compatibility by **default** will be in effect starting with 3.0.
 * Importantly though, N/N-2 compatibility is still guaranteed with the proper
 * configurations set.
 *
 * Further to distinguish unspecified `minVersionForCollab` from a specified
 * version and allow `enableExplicitSchemaControl` to default to `true` for
 * any 2.0.0+ version, we will use a special value of `2.0.0-defaults`, which
 * is semantically less than 2.0.0.
 */
exports.defaultMinVersionForCollab = "2.0.0-defaults";
/**
 * We don't want allow a version before the major public release of the LTS version.
 * Today we use "1.0.0", because our policy supports N/N-1 & N/N-2, which includes
 * all minor versions of N. Though LTS starts at 1.4.0, we should stay consistent
 * with our policy and allow all 1.x versions to be compatible with 2.x.
 */
const lowestMinVersionForCollab = "1.0.0";
/**
 * Mapping of RuntimeOptionsAffectingDocSchema to their compatibility related configs.
 *
 * Each key in this map corresponds to a property in RuntimeOptionsAffectingDocSchema. The value is an object that maps MinimumVersionForCollab
 * to the appropriate default value for that property to supporting that MinimumVersionForCollab. If clients running MinimumVersionForCollab X are able to understand
 * the format changes introduced by the property, then the default value for that MinimumVersionForCollab will enable the feature associated with the property.
 * Otherwise, the feature will be disabled.
 *
 * For example if the minVersionForCollab is a 1.x version (i.e. "1.5.0"), then the default value for `enableGroupedBatching` will be false since 1.x
 * clients do not understand the document format when batching is enabled. If the minVersionForCollab is a 2.x client (i.e. "2.0.0" or later), then the
 * default value for `enableGroupedBatching` will be true because clients running 2.0 or later will be able to understand the format changes associated
 * with the batching feature.
 */
const runtimeOptionsAffectingDocSchemaConfigMap = {
    enableGroupedBatching: {
        "1.0.0": false,
        "2.0.0-defaults": true,
    },
    compressionOptions: {
        "1.0.0": compressionDefinitions_js_1.disabledCompressionConfig,
        "2.0.0-defaults": compressionDefinitions_js_1.enabledCompressionConfig,
    },
    enableRuntimeIdCompressor: {
        // For IdCompressorMode, `undefined` represents a logical state (off).
        // However, to satisfy the Required<> constraint while
        // `exactOptionalPropertyTypes` is `false` (TODO: AB#8215), we need
        // to have it defined, so we trick the type checker here.
        "1.0.0": undefined,
        // We do not yet want to enable idCompressor by default since it will
        // increase bundle sizes, and not all customers will benefit from it.
        // Therefore, we will require customers to explicitly enable it. We
        // are keeping it as a DocSchema affecting option for now as this may
        // change in the future.
    },
    explicitSchemaControl: {
        "1.0.0": false,
        // This option's intention is to prevent 1.x clients from joining sessions
        // when enabled. This is set to true when the minVersionForCollab is set
        // to >=2.0.0 (explicitly). This is different than other 2.0 defaults
        // because it was not enabled by default prior to the implementation of
        // `minVersionForCollab`.
        // `defaultMinVersionForCollab` is set to "2.0.0-defaults" which "2.0.0"
        // does not satisfy to avoiding enabling this option by default as of
        // `minVersionForCollab` introduction, which could be unexpected.
        // Only enable as a default when `minVersionForCollab` is specified at
        // 2.0.0+.
        "2.0.0": true,
    },
    flushMode: {
        // Note: 1.x clients are compatible with TurnBased flushing, but here we elect to remain on Immediate flush mode
        // as a work-around for inability to send batches larger than 1Mb. Immediate flushing keeps batches smaller as
        // fewer messages will be included per flush.
        "1.0.0": internal_2.FlushMode.Immediate,
        "2.0.0-defaults": internal_2.FlushMode.TurnBased,
    },
    gcOptions: {
        "1.0.0": {},
        // Although sweep is supported in 2.x, it is disabled by default until minVersionForCollab>=3.0.0 to be extra safe.
        "3.0.0": { enableGCSweep: true },
    },
    createBlobPayloadPending: {
        // This feature is new and disabled by default. In the future we will enable it by default, but we have not
        // closed on the version where that will happen yet.  Probably a .10 release since blob functionality is not
        // exposed on the `@public` API surface.
        "1.0.0": undefined,
    },
};
const runtimeOptionsAffectingDocSchemaConfigValidationMap = {
    enableGroupedBatching: configValueToMinVersionForCollab([
        [false, "1.0.0"],
        [true, "2.0.0-defaults"],
    ]),
    compressionOptions: configValueToMinVersionForCollab([
        [{ ...compressionDefinitions_js_1.disabledCompressionConfig }, "1.0.0"],
        [{ ...compressionDefinitions_js_1.enabledCompressionConfig }, "2.0.0-defaults"],
    ]),
    enableRuntimeIdCompressor: configValueToMinVersionForCollab([
        [undefined, "1.0.0"],
        ["on", "2.0.0-defaults"],
        ["delayed", "2.0.0-defaults"],
    ]),
    explicitSchemaControl: configValueToMinVersionForCollab([
        [false, "1.0.0"],
        [true, "2.0.0-defaults"],
    ]),
    flushMode: configValueToMinVersionForCollab([
        [internal_2.FlushMode.Immediate, "1.0.0"],
        [internal_2.FlushMode.TurnBased, "2.0.0-defaults"],
    ]),
    gcOptions: configValueToMinVersionForCollab([
        [{ enableGCSweep: undefined }, "1.0.0"],
        [{ enableGCSweep: true }, "2.0.0-defaults"],
    ]),
    createBlobPayloadPending: configValueToMinVersionForCollab([
        [undefined, "1.0.0"],
        [true, "2.40.0"],
    ]),
};
/**
 * Returns the default RuntimeOptionsAffectingDocSchema configuration for a given minVersionForCollab.
 */
function getMinVersionForCollabDefaults(minVersionForCollab) {
    return getConfigsForMinVersionForCollab(minVersionForCollab, runtimeOptionsAffectingDocSchemaConfigMap);
}
exports.getMinVersionForCollabDefaults = getMinVersionForCollabDefaults;
/**
 * Returns a default configuration given minVersionForCollab and configuration version map.
 */
function getConfigsForMinVersionForCollab(minVersionForCollab, configMap) {
    const defaultConfigs = {};
    // Iterate over configMap to get default values for each option.
    for (const key of Object.keys(configMap)) {
        const config = configMap[key];
        // Sort the versions in ascending order so we can short circuit the loop.
        const versions = Object.keys(config).sort(semver_ts_1.compare);
        // For each config, we iterate over the keys and check if minVersionForCollab is greater than or equal to the version.
        // If so, we set it as the default value for the option. At the end of the loop we should have the most recent default
        // value that is compatible with the version specified as the minVersionForCollab.
        for (const version of versions) {
            if ((0, semver_ts_1.gte)(minVersionForCollab, version)) {
                defaultConfigs[key] = config[version];
            }
            else {
                // If the minVersionForCollab is less than the version, we break out of the loop since we don't need to check
                // any later versions.
                break;
            }
        }
    }
    return defaultConfigs;
}
exports.getConfigsForMinVersionForCollab = getConfigsForMinVersionForCollab;
/**
 * Checks if the minVersionForCollab is valid.
 * A valid minVersionForCollab is a MinimumVersionForCollab that is at least `lowestMinVersionForCollab` and less than or equal to the current package version.
 */
function isValidMinVersionForCollab(minVersionForCollab) {
    return ((0, semver_ts_1.valid)(minVersionForCollab) !== null &&
        (0, semver_ts_1.gte)(minVersionForCollab, lowestMinVersionForCollab) &&
        (0, semver_ts_1.lte)(minVersionForCollab, packageVersion_js_1.pkgVersion));
}
exports.isValidMinVersionForCollab = isValidMinVersionForCollab;
/**
 * Validates if the runtime options passed in from the user are compatible with the minVersionForCollab.
 * For example, if a user sets the `enableGroupedBatching` option to true, but the minVersionForCollab
 * is set to "1.0.0", then we should throw a UsageError since 1.x clients do not support batching.
 * */
function validateRuntimeOptions(minVersionForCollab, runtimeOptions) {
    getValidationForRuntimeOptions(minVersionForCollab, runtimeOptions, runtimeOptionsAffectingDocSchemaConfigValidationMap);
}
exports.validateRuntimeOptions = validateRuntimeOptions;
/**
 * Generic function to validate runtime options against the minVersionForCollab.
 */
function getValidationForRuntimeOptions(minVersionForCollab, runtimeOptions, validationMap) {
    if (minVersionForCollab === exports.defaultMinVersionForCollab) {
        // If the minVersionForCollab is set to the default value, then we will not validate the runtime options
        // This is to avoid disruption to users who have not yet set the minVersionForCollab value explicitly.
        return;
    }
    // Iterate through each runtime option passed in by the user
    for (const [passedRuntimeOption, passedRuntimeOptionValue] of Object.entries(runtimeOptions)) {
        // Skip if passedRuntimeOption is not in validation map
        if (!(passedRuntimeOption in validationMap)) {
            continue;
        }
        const requiredVersion = validationMap[passedRuntimeOption](passedRuntimeOptionValue);
        if (requiredVersion !== undefined && (0, semver_ts_1.gt)(requiredVersion, minVersionForCollab)) {
            throw new internal_3.UsageError(`Runtime option ${passedRuntimeOption}:${JSON.stringify(passedRuntimeOptionValue)} requires ` +
                `runtime version ${requiredVersion}. Please update minVersionForCollab ` +
                `(currently ${minVersionForCollab}) to ${requiredVersion} or later to proceed.`);
        }
    }
}
exports.getValidationForRuntimeOptions = getValidationForRuntimeOptions;
/**
 * Helper function to map ContainerRuntimeOptionsInternal config values to
 * minVersionForCollab in {@link runtimeOptionsAffectingDocSchemaConfigValidationMap}.
 */
function configValueToMinVersionForCollab(configToMinVer) {
    const configValueToRequiredVersionMap = new Map(configToMinVer);
    return (configValue) => {
        // If the configValue is not an object then we can get the version required directly from the map.
        if (typeof configValue !== "object") {
            return configValueToRequiredVersionMap.get(configValue);
        }
        // When the input `configValue` is an object, this logic determines the minimum runtime version it requires.
        // It iterates through each entry in `configValueToRequiredVersionMap`. If `possibleConfigValue` shares at
        // least one key-value pair with the input `configValue`, its associated `versionRequired` is collected into
        // `matchingVersions`. After checking all entries, the highest among the collected versions is returned.
        // This represents the overall minimum version required to support the features implied by the input `configValue`.
        const matchingVersions = [];
        for (const [possibleConfigValue, versionRequired,] of configValueToRequiredVersionMap.entries()) {
            (0, internal_1.assert)(typeof possibleConfigValue == "object", 0xbb9 /* possibleConfigValue should be an object */);
            // Check if `possibleConfigValue` and the input `configValue` share at least one
            // common key-value pair. If they do, the `versionRequired` for this `possibleConfigValue`
            // is added to `matchingVersions`.
            if (Object.entries(possibleConfigValue).some(([k, v]) => configValue[k] === v)) {
                matchingVersions.push(versionRequired);
            }
        }
        if (matchingVersions.length > 0) {
            // Return the latest minVersionForCollab among all matches.
            return matchingVersions.sort((a, b) => (0, semver_ts_1.compare)(b, a))[0];
        }
        // If no matches then we return undefined. This means that the config value passed in
        // does not require a specific minVersionForCollab to be valid.
        return undefined;
    };
}
exports.configValueToMinVersionForCollab = configValueToMinVersionForCollab;
//# sourceMappingURL=compatUtils.js.map