"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.ViewSchema = void 0;
const util_1 = require("../../util");
const core_1 = require("../../core");
const comparison_1 = require("./comparison");
/**
 * A collection of View information for schema, including policy.
 */
class ViewSchema extends core_1.ViewSchemaData {
    constructor(policy, adapters, schema) {
        super(policy, adapters);
        this.schema = schema;
    }
    /**
     * Determines the compatibility of a stored document
     * (based on its stored schema) with a viewer (based on its view schema).
     *
     * Adapters can be provided to handle differences between the two schema.
     * Adapters should only use to types in the `view` SchemaRepository.
     *
     * TODO: this API violates the parse don't validate design philosophy.
     * It should be wrapped with (or replaced by) a parse style API.
     */
    checkCompatibility(stored) {
        const adapted = this.adaptRepo(stored);
        const read = (0, comparison_1.allowsRepoSuperset)(this.policy, stored, this.schema)
            ? core_1.Compatibility.Compatible
            : (0, comparison_1.allowsRepoSuperset)(this.policy, adapted.adaptedForViewSchema, this.schema)
                ? core_1.Compatibility.RequiresAdapters
                : core_1.Compatibility.Incompatible;
        // TODO: Extract subset of adapters that are valid to use on stored
        // TODO: separate adapters from schema updates
        const write = (0, comparison_1.allowsRepoSuperset)(this.policy, this.schema, stored)
            ? core_1.Compatibility.Compatible
            : (0, comparison_1.allowsRepoSuperset)(this.policy, this.schema, adapted.adaptedForViewSchema)
                ? // TODO: IThis assumes adapters are bidirectional.
                    core_1.Compatibility.RequiresAdapters
                : core_1.Compatibility.Incompatible;
        // TODO: compute this properly (and maybe include the set of schema changes needed for it?).
        // Maybe updates would happen lazily when needed to store data?
        // When willingness to updates can avoid need for some adapters,
        // how should it be decided if the adapter should be used to avoid the update?
        // TODO: is this case actually bi-variant, making this correct if we did it for each schema independently?
        let writeAllowingStoredSchemaUpdates = 
        // TODO: This should consider just the updates needed
        // (ex: when view covers a subset of stored after stored has a update to that subset).
        (0, comparison_1.allowsRepoSuperset)(this.policy, stored, this.schema)
            ? core_1.Compatibility.Compatible
            : // TODO: this assumes adapters can translate in both directions. In general this will not be true.
                // TODO: this also assumes that schema updates to the adapted repo would translate to
                // updates on the stored schema, which is also likely untrue.
                (0, comparison_1.allowsRepoSuperset)(this.policy, adapted.adaptedForViewSchema, this.schema)
                    ? core_1.Compatibility.RequiresAdapters // Requires schema updates. TODO: consider adapters that can update writes.
                    : core_1.Compatibility.Incompatible;
        // Since the above does not consider partial updates,
        // we can improve the tolerance a bit by considering the op-op update:
        writeAllowingStoredSchemaUpdates = Math.max(writeAllowingStoredSchemaUpdates, write);
        return { read, write, writeAllowingStoredSchemaUpdates };
    }
    /**
     * Compute a schema that `original` could be viewed as using adapters as needed.
     *
     * TODO: have a way for callers to get invalidated on schema updates.
     * Maybe pass in StoredSchemaRepository and optional ObservingDependent?
     */
    adaptRepo(stored) {
        var _a, _b, _c, _d;
        // Sanity check on adapters:
        // it's probably a bug it they use the never types,
        // since there never is a reason to have a never type as an adapter input,
        // and its impossible for an adapter to be correctly implemented if its output type is never
        // (unless its input is also never).
        for (const adapter of (_b = (_a = this.adapters) === null || _a === void 0 ? void 0 : _a.tree) !== null && _b !== void 0 ? _b : []) {
            if ((0, comparison_1.isNeverTree)(this.policy, this.schema, (_c = this.schema.treeSchema.get(adapter.output)) !== null && _c !== void 0 ? _c : this.policy.defaultTreeSchema)) {
                (0, util_1.fail)("tree adapter for stored adapter.output should not be never");
            }
        }
        const adapted = {
            globalFieldSchema: new Map(),
            treeSchema: new Map(),
        };
        for (const [key, schema] of stored.globalFieldSchema) {
            const adaptedField = this.adaptField(schema, (_d = this.adapters.fieldAdapters) === null || _d === void 0 ? void 0 : _d.get(key));
            adapted.globalFieldSchema.set(key, adaptedField);
        }
        for (const [key, schema] of stored.treeSchema) {
            const adapatedTree = this.adaptTree(schema);
            adapted.treeSchema.set(key, adapatedTree);
        }
        // TODO: subset these adapters to the ones that were needed/used.
        return new core_1.AdaptedViewSchema(this.adapters, adapted);
    }
    /**
     * Adapt original such that it allows member types which can be adapted to its specified types.
     */
    adaptField(original, adapter) {
        var _a, _b, _c, _d, _e, _f;
        if (original.types !== undefined) {
            const types = new Set(original.types);
            for (const treeAdapter of (_b = (_a = this.adapters) === null || _a === void 0 ? void 0 : _a.tree) !== null && _b !== void 0 ? _b : []) {
                if (types.has(treeAdapter.input)) {
                    types.delete(treeAdapter.input);
                    types.add(treeAdapter.output);
                }
            }
            return ((_d = (_c = adapter === null || adapter === void 0 ? void 0 : adapter.convert) === null || _c === void 0 ? void 0 : _c.call(adapter, { kind: original.kind, types })) !== null && _d !== void 0 ? _d : { kind: original.kind, types });
        }
        return (_f = (_e = adapter === null || adapter === void 0 ? void 0 : adapter.convert) === null || _e === void 0 ? void 0 : _e.call(adapter, original)) !== null && _f !== void 0 ? _f : original;
    }
    adaptTree(original) {
        const localFields = new Map();
        for (const [key, schema] of original.localFields) {
            // TODO: support missing field adapters for local fields.
            localFields.set(key, this.adaptField(schema, undefined));
        }
        // Would be nice to use ... here, but some implementations can use properties as well as have extra fields,
        // so copying the data over manually is better.
        return {
            globalFields: original.globalFields,
            extraLocalFields: original.extraLocalFields,
            extraGlobalFields: original.extraGlobalFields,
            value: original.value,
            localFields,
        };
    }
}
exports.ViewSchema = ViewSchema;
{
}
//# sourceMappingURL=view.js.map