"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.FieldKinds = exports.fieldKinds = exports.forbidden = exports.sequence = exports.optional = exports.value = exports.counter = exports.counterHandle = exports.noChangeHandler = exports.replaceRebaser = exports.lastWriteWinsRebaser = void 0;
const common_utils_1 = require("@fluidframework/common-utils");
const codec_1 = require("../codec");
const core_1 = require("../core");
const util_1 = require("../util");
const treeTextCursor_1 = require("./treeTextCursor");
const modular_schema_1 = require("./modular-schema");
const sequence_field_1 = require("./sequence-field");
const deltaUtils_1 = require("./deltaUtils");
function brandedFieldKind(identifier, multiplicity, changeHandler, allowsTreeSupersetOf, handlesEditsFrom) {
    return new modular_schema_1.FieldKind((0, util_1.brand)(identifier), multiplicity, changeHandler, allowsTreeSupersetOf, handlesEditsFrom);
}
/**
 * @returns a ChangeRebaser that assumes all the changes commute, meaning that order does not matter.
 */
function commutativeRebaser(data) {
    const rebase = (change, _over) => change;
    return (0, modular_schema_1.referenceFreeFieldChangeRebaser)(Object.assign(Object.assign({}, data), { rebase }));
}
/**
 * Picks the last value written.
 *
 * TODO: it seems impossible for this to obey the desired axioms.
 * Specifically inverse needs to cancel, restoring the value from the previous change which was discarded.
 */
function lastWriteWinsRebaser(data) {
    const compose = (changes) => changes.length >= 0 ? changes[changes.length - 1] : data.noop;
    const rebase = (change, _over) => change;
    return (0, modular_schema_1.referenceFreeFieldChangeRebaser)(Object.assign(Object.assign({}, data), { compose, rebase }));
}
exports.lastWriteWinsRebaser = lastWriteWinsRebaser;
/**
 * Picks the last value written.
 *
 * Consistent if used on valid paths with correct old states.
 */
function replaceRebaser() {
    return (0, modular_schema_1.referenceFreeFieldChangeRebaser)({
        rebase: (change, over) => {
            if (change === 0) {
                return 0;
            }
            if (over === 0) {
                return change;
            }
            return { old: over.new, new: change.new };
        },
        compose: (changes) => {
            const f = changes.filter((c) => c !== 0);
            if (f.length === 0) {
                return 0;
            }
            for (let index = 1; index < f.length; index++) {
                (0, common_utils_1.assert)(f[index - 1].new === f[index].old, 0x3a4 /* adjacent replaces must match */);
            }
            return { old: f[0].old, new: f[f.length - 1].new };
        },
        invert: (changes) => {
            return changes === 0 ? 0 : { old: changes.new, new: changes.old };
        },
    });
}
exports.replaceRebaser = replaceRebaser;
/**
 * ChangeHandler that only handles no-op / identity changes.
 */
exports.noChangeHandler = {
    rebaser: (0, modular_schema_1.referenceFreeFieldChangeRebaser)({
        compose: (changes) => 0,
        invert: (changes) => 0,
        rebase: (change, over) => 0,
    }),
    codecsFactory: () => (0, codec_1.makeCodecFamily)([[0, codec_1.unitCodec]]),
    editor: { buildChildChange: (index, change) => (0, util_1.fail)("Child changes not supported") },
    intoDelta: (change, deltaFromChild) => [],
    isEmpty: (change) => true,
};
/**
 * ChangeHandler that does not support any changes.
 *
 * TODO: Due to floating point precision compose is not quite associative.
 * This may violate our requirements.
 * This could be fixed by making this integer only
 * and handling values past Number.MAX_SAFE_INTEGER (ex: via an arbitrarily large integer library)
 * or via modular arithmetic.
 */
exports.counterHandle = {
    rebaser: commutativeRebaser({
        compose: (changes) => changes.reduce((a, b) => a + b, 0),
        invert: (change) => -change,
    }),
    codecsFactory: () => (0, codec_1.makeCodecFamily)([[0, (0, codec_1.makeValueCodec)()]]),
    editor: { buildChildChange: (index, change) => (0, util_1.fail)("Child changes not supported") },
    intoDelta: (change, deltaFromChild) => [
        {
            type: core_1.Delta.MarkType.Modify,
            setValue: change,
        },
    ],
    isEmpty: (change) => change === 0,
};
/**
 * Field kind for counters.
 * Stores a single value which corresponds to number which can be added to.
 *
 * This is an example of a few interesting things:
 *
 * - A field kind with some constraints on what can be under it type wise.
 * Other possible examples which would do this include sets, maps (for their keys),
 * or any domain specific specialized kinds.
 *
 * - A field kind with commutative edits.
 *
 * TODO:
 * What should the subtrees under this look like?
 * How does it prevent / interact with direct edits to the subtree (ex: set value)?
 * How should it use its type set?
 * How should it handle lack of associative addition due to precision and overflow?
 */
exports.counter = brandedFieldKind("Counter", modular_schema_1.Multiplicity.Value, exports.counterHandle, (types, other) => other.kind.identifier === exports.counter.identifier, new Set());
const valueRebaser = (0, modular_schema_1.isolatedFieldChangeRebaser)({
    compose: (changes, composeChildren) => {
        if (changes.length === 0) {
            return {};
        }
        let newValue;
        const childChanges = [];
        for (const { change, revision } of changes) {
            if (change.value !== undefined) {
                newValue = change.value;
                // The previous changes applied to a different value, so we discard them.
                // TODO: Consider if we should represent muted changes
                childChanges.length = 0;
            }
            if (change.changes !== undefined) {
                childChanges.push((0, core_1.tagChange)(change.changes, revision));
            }
        }
        const composed = {};
        if (newValue !== undefined) {
            composed.value = newValue;
        }
        if (childChanges.length > 0) {
            composed.changes = composeChildren(childChanges);
        }
        return composed;
    },
    invert: ({ revision, change }, invertChild, reviver) => {
        const inverse = {};
        if (change.changes !== undefined) {
            inverse.changes = invertChild(change.changes, 0);
        }
        if (change.value !== undefined) {
            (0, common_utils_1.assert)(revision !== undefined, 0x591 /* Unable to revert to undefined revision */);
            inverse.value = { revert: reviver(revision, 0, 1)[0], revision };
        }
        return inverse;
    },
    rebase: (change, over, rebaseChild) => {
        if (change.changes === undefined || over.change.changes === undefined) {
            return change;
        }
        return Object.assign(Object.assign({}, change), { changes: rebaseChild(change.changes, over.change.changes) });
    },
});
function makeValueFieldCodec(childCodec) {
    return {
        encode: (change) => {
            const encoded = {};
            if (change.value !== undefined) {
                encoded.value = encodeNodeUpdate(change.value, childCodec);
            }
            if (change.changes !== undefined) {
                encoded.changes = childCodec.encode(change.changes);
            }
            return encoded;
        },
        decode: (change) => {
            const encoded = change;
            const decoded = {};
            if (encoded.value !== undefined) {
                decoded.value = decodeNodeUpdate(encoded.value, childCodec);
            }
            if (encoded.changes !== undefined) {
                decoded.changes = childCodec.decode(encoded.changes);
            }
            return decoded;
        },
    };
}
function encodeNodeUpdate(update, childCodec) {
    const encoded = "revert" in update
        ? {
            revert: (0, treeTextCursor_1.jsonableTreeFromCursor)(update.revert),
            revision: update.revision,
        }
        : {
            set: update.set,
        };
    if (update.changes !== undefined) {
        encoded.changes = childCodec.encode(update.changes);
    }
    return encoded;
}
function decodeNodeUpdate(encoded, childCodec) {
    const decoded = "revert" in encoded
        ? {
            revert: (0, treeTextCursor_1.singleTextCursor)(encoded.revert),
            revision: encoded.revision,
        }
        : { set: encoded.set };
    if (encoded.changes !== undefined) {
        decoded.changes = childCodec.decode(encoded.changes);
    }
    return decoded;
}
const valueFieldEditor = {
    buildChildChange: (index, change) => {
        (0, common_utils_1.assert)(index === 0, 0x3b6 /* Value fields only support a single child node */);
        return { changes: change };
    },
    set: (newValue) => ({ value: { set: (0, treeTextCursor_1.jsonableTreeFromCursor)(newValue) } }),
};
const valueChangeHandler = {
    rebaser: valueRebaser,
    codecsFactory: (childCodec) => (0, codec_1.makeCodecFamily)([[0, makeValueFieldCodec(childCodec)]]),
    editor: valueFieldEditor,
    intoDelta: (change, deltaFromChild) => {
        if (change.value !== undefined) {
            const newValue = "revert" in change.value ? change.value.revert : (0, treeTextCursor_1.singleTextCursor)(change.value.set);
            const insertDelta = deltaFromInsertAndChange(newValue, change.changes, deltaFromChild);
            return [{ type: core_1.Delta.MarkType.Delete, count: 1 }, ...insertDelta];
        }
        return change.changes === undefined ? [] : [deltaFromChild(change.changes)];
    },
    isEmpty: (change) => change.changes === undefined && change.value === undefined,
};
/**
 * Exactly one item.
 */
exports.value = brandedFieldKind("Value", modular_schema_1.Multiplicity.Value, valueChangeHandler, (types, other) => (other.kind.identifier === exports.sequence.identifier ||
    other.kind.identifier === exports.value.identifier ||
    other.kind.identifier === exports.optional.identifier) &&
    (0, modular_schema_1.allowsTreeSchemaIdentifierSuperset)(types, other.types), new Set());
const optionalChangeRebaser = (0, modular_schema_1.isolatedFieldChangeRebaser)({
    compose: (changes, composeChild) => {
        var _a;
        let fieldChange;
        const origNodeChange = [];
        const newNodeChanges = [];
        for (const { change, revision } of changes) {
            if (change.deletedBy === undefined && change.childChange !== undefined) {
                const taggedChange = (0, core_1.tagChange)(change.childChange, revision);
                if (fieldChange === undefined) {
                    origNodeChange.push(taggedChange);
                }
                else {
                    newNodeChanges.push(taggedChange);
                }
            }
            if (change.fieldChange !== undefined) {
                if (fieldChange === undefined) {
                    fieldChange = { wasEmpty: change.fieldChange.wasEmpty };
                }
                if (change.fieldChange.newContent !== undefined) {
                    fieldChange.newContent = Object.assign({}, change.fieldChange.newContent);
                }
                else {
                    delete fieldChange.newContent;
                }
                // The previous changes applied to a different value, so we discard them.
                // TODO: Represent muted changes
                newNodeChanges.length = 0;
                if (((_a = change.fieldChange.newContent) === null || _a === void 0 ? void 0 : _a.changes) !== undefined) {
                    newNodeChanges.push((0, core_1.tagChange)(change.fieldChange.newContent.changes, revision));
                }
            }
        }
        const composed = {};
        if (fieldChange !== undefined) {
            if (newNodeChanges.length > 0) {
                (0, common_utils_1.assert)(fieldChange.newContent !== undefined, 0x5c4 /* Shouldn't have new node changes if there is no new node */);
                fieldChange.newContent.changes = composeChild(newNodeChanges);
            }
            composed.fieldChange = fieldChange;
        }
        if (origNodeChange.length > 0) {
            composed.childChange = composeChild(origNodeChange);
        }
        return composed;
    },
    invert: ({ revision, change }, invertChild, reviver) => {
        var _a;
        const inverse = {};
        const fieldChange = change.fieldChange;
        if (fieldChange !== undefined) {
            inverse.fieldChange = { wasEmpty: fieldChange.newContent === undefined };
            if (((_a = fieldChange.newContent) === null || _a === void 0 ? void 0 : _a.changes) !== undefined) {
                // The node inserted by change will be the node deleted by inverse
                // Move the inverted changes to the child change field
                inverse.childChange = invertChild(fieldChange.newContent.changes, 0);
            }
            if (!fieldChange.wasEmpty) {
                (0, common_utils_1.assert)(revision !== undefined, 0x592 /* Unable to revert to undefined revision */);
                inverse.fieldChange.newContent = { revert: reviver(revision, 0, 1)[0], revision };
                if (change.childChange !== undefined) {
                    if (change.deletedBy === undefined) {
                        inverse.fieldChange.newContent.changes = invertChild(change.childChange, 0);
                    }
                    else {
                        // We currently drop the muted changes in the inverse.
                        // TODO: produce muted inverse changes so that a retroactive undo of revision
                        // `change.deletedBy` would be able to pick up and unmute those changes.
                    }
                }
            }
        }
        else {
            if (change.childChange !== undefined && change.deletedBy === undefined) {
                inverse.childChange = invertChild(change.childChange, 0);
            }
            else {
                // Drop the muted changes if deletedBy is set to avoid
                // applying muted changes on undo
            }
        }
        return inverse;
    },
    rebase: (change, overTagged, rebaseChild) => {
        const over = overTagged.change;
        if (change.fieldChange !== undefined) {
            if (over.fieldChange !== undefined) {
                const wasEmpty = over.fieldChange.newContent === undefined;
                // TODO: Handle rebasing child changes over `over.childChange`.
                return Object.assign(Object.assign({}, change), { fieldChange: Object.assign(Object.assign({}, change.fieldChange), { wasEmpty }) });
            }
            const rebasedChange = Object.assign({}, change);
            const overChildChange = change.deletedBy === over.deletedBy ? over.childChange : undefined;
            const rebasedChildChange = rebaseChild(change.childChange, overChildChange);
            if (rebasedChildChange !== undefined) {
                rebasedChange.childChange = rebasedChildChange;
            }
            else {
                delete rebasedChange.childChange;
            }
            return rebasedChange;
        }
        if (change.childChange !== undefined) {
            if (over.fieldChange !== undefined) {
                if (change.deletedBy === undefined) {
                    // `change.childChange` refers to the node being deleted by `over`.
                    return {
                        childChange: rebaseChild(change.childChange, over.deletedBy === undefined ? undefined : over.childChange),
                        deletedBy: overTagged.revision,
                    };
                }
                else if (over.fieldChange.newContent !== undefined &&
                    "revert" in over.fieldChange.newContent &&
                    over.fieldChange.newContent.revision === change.deletedBy) {
                    // Over is reviving the node that change.childChange is referring to.
                    // Rebase change.childChange and remove deletedBy
                    // because we revived the node that childChange refers to
                    return {
                        childChange: rebaseChild(change.childChange, over.fieldChange.newContent.changes),
                    };
                }
            }
        }
        {
            const rebasedChange = Object.assign({}, change);
            let overChildChange;
            if (change.deletedBy === undefined && over.deletedBy === undefined) {
                overChildChange = over.childChange;
            }
            const rebasedChildChange = rebaseChild(change.childChange, overChildChange);
            if (rebasedChildChange !== undefined) {
                rebasedChange.childChange = rebasedChildChange;
            }
            else {
                delete rebasedChange.childChange;
            }
            return rebasedChange;
        }
    },
});
const optionalFieldEditor = {
    set: (newContent, wasEmpty) => ({
        fieldChange: {
            newContent: newContent === undefined
                ? undefined
                : {
                    set: (0, treeTextCursor_1.jsonableTreeFromCursor)(newContent),
                },
            wasEmpty,
        },
    }),
    buildChildChange: (index, childChange) => {
        (0, common_utils_1.assert)(index === 0, 0x404 /* Optional fields only support a single child node */);
        return { childChange };
    },
};
const optionalFieldCodecFamilyFactory = (childCodec) => (0, codec_1.makeCodecFamily)([
    [
        0,
        {
            encode: (change) => {
                const encoded = {};
                if (change.fieldChange !== undefined) {
                    encoded.fieldChange = { wasEmpty: change.fieldChange.wasEmpty };
                    if (change.fieldChange.newContent !== undefined) {
                        encoded.fieldChange.newContent = encodeNodeUpdate(change.fieldChange.newContent, childCodec);
                    }
                }
                if (change.childChange !== undefined) {
                    encoded.childChange = childCodec.encode(change.childChange);
                }
                return encoded;
            },
            decode: (change) => {
                const encoded = change;
                const decoded = {};
                if (encoded.fieldChange !== undefined) {
                    decoded.fieldChange = {
                        wasEmpty: encoded.fieldChange.wasEmpty,
                    };
                    if (encoded.fieldChange.newContent !== undefined) {
                        decoded.fieldChange.newContent = decodeNodeUpdate(encoded.fieldChange.newContent, childCodec);
                    }
                }
                if (encoded.childChange !== undefined) {
                    decoded.childChange = childCodec.decode(encoded.childChange);
                }
                return decoded;
            },
        },
    ],
]);
function deltaFromInsertAndChange(insertedContent, nodeChange, deltaFromNode) {
    if (insertedContent !== undefined) {
        const insert = {
            type: core_1.Delta.MarkType.Insert,
            content: [insertedContent],
        };
        if (nodeChange !== undefined) {
            const nodeDelta = deltaFromNode(nodeChange);
            (0, deltaUtils_1.populateChildModifications)(nodeDelta, insert);
        }
        return [insert];
    }
    if (nodeChange !== undefined) {
        return [deltaFromNode(nodeChange)];
    }
    return [];
}
function deltaForDelete(nodeExists, nodeChange, deltaFromNode) {
    if (!nodeExists) {
        return [];
    }
    const deleteDelta = { type: core_1.Delta.MarkType.Delete, count: 1 };
    if (nodeChange !== undefined) {
        const modify = deltaFromNode(nodeChange);
        deleteDelta.setValue = modify.setValue;
        deleteDelta.fields = modify.fields;
    }
    return [deleteDelta];
}
/**
 * 0 or 1 items.
 */
exports.optional = brandedFieldKind("Optional", modular_schema_1.Multiplicity.Optional, {
    rebaser: optionalChangeRebaser,
    codecsFactory: optionalFieldCodecFamilyFactory,
    editor: optionalFieldEditor,
    intoDelta: (change, deltaFromChild) => {
        var _a;
        if (change.fieldChange === undefined) {
            if (change.deletedBy === undefined && change.childChange !== undefined) {
                return [deltaFromChild(change.childChange)];
            }
            return [];
        }
        const deleteDelta = deltaForDelete(!change.fieldChange.wasEmpty, change.deletedBy === undefined ? change.childChange : undefined, deltaFromChild);
        const update = (_a = change.fieldChange) === null || _a === void 0 ? void 0 : _a.newContent;
        let content;
        if (update === undefined) {
            content = undefined;
        }
        else if ("set" in update) {
            content = (0, treeTextCursor_1.singleTextCursor)(update.set);
        }
        else {
            content = update.revert;
        }
        const insertDelta = deltaFromInsertAndChange(content, update === null || update === void 0 ? void 0 : update.changes, deltaFromChild);
        return [...deleteDelta, ...insertDelta];
    },
    isEmpty: (change) => change.childChange === undefined && change.fieldChange === undefined,
}, (types, other) => (other.kind.identifier === exports.sequence.identifier ||
    other.kind.identifier === exports.optional.identifier) &&
    (0, modular_schema_1.allowsTreeSchemaIdentifierSuperset)(types, other.types), new Set([exports.value.identifier]));
/**
 * 0 or more items.
 */
exports.sequence = brandedFieldKind("Sequence", modular_schema_1.Multiplicity.Sequence, sequence_field_1.sequenceFieldChangeHandler, (types, other) => other.kind.identifier === exports.sequence.identifier &&
    (0, modular_schema_1.allowsTreeSchemaIdentifierSuperset)(types, other.types), 
// TODO: add normalizer/importers for handling ops from other kinds.
new Set([]));
/**
 * Exactly 0 items.
 *
 * Using Forbidden makes what types are listed for allowed in a field irrelevant
 * since the field will never have values in it.
 *
 * Using Forbidden is equivalent to picking a kind that permits empty (like sequence or optional)
 * and having no allowed types (or only never types).
 * Because of this, its possible to express everything constraint wise without Forbidden,
 * but using Forbidden can be more semantically clear than optional with no allowed types.
 *
 * For view schema, this can be useful if you need to:
 * - run a specific out of schema handler when a field is present,
 * but otherwise are ignoring or tolerating (ex: via extra fields) unmentioned fields.
 * - prevent a specific field from being used as an extra field
 * (perhaps for some past of future compatibility reason)
 * - keep a field in a schema for metadata purposes
 * (ex: for improved error messaging, error handling or documentation)
 * that is not used in this specific version of the schema (ex: to document what it was or will be used for).
 *
 * For stored schema, this can be useful if you need to:
 * - have a field which can have its schema updated to Optional or Sequence of any type.
 * - to exclude a field from extra fields
 * - for the schema system to use as a default for fields which aren't declared
 * (ex: when updating a field that did not exist into one that does)
 *
 * See {@link emptyField} for a constant, reusable field using Forbidden.
 */
exports.forbidden = brandedFieldKind("Forbidden", modular_schema_1.Multiplicity.Forbidden, exports.noChangeHandler, 
// All multiplicities other than Value support empty.
(types, other) => { var _a; return ((_a = exports.fieldKinds.get(other.kind.identifier)) === null || _a === void 0 ? void 0 : _a.multiplicity) !== modular_schema_1.Multiplicity.Value; }, new Set());
/**
 * Default field kinds by identifier
 */
exports.fieldKinds = new Map([exports.value, exports.optional, exports.sequence, exports.forbidden, exports.counter].map((s) => [s.identifier, s]));
/**
 * Default FieldKinds with their editor types erased.
 * @alpha
 */
exports.FieldKinds = { value: exports.value, optional: exports.optional, sequence: exports.sequence, forbidden: exports.forbidden };
//# sourceMappingURL=defaultFieldKinds.js.map