"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.createPropertyTrackingAndInsertionAttributionPolicyFactory = exports.createPropertyTrackingAttributionPolicyFactory = exports.createInsertOnlyAttributionPolicy = void 0;
const common_utils_1 = require("@fluidframework/common-utils");
const mergeTreeDeltaCallback_1 = require("./mergeTreeDeltaCallback");
const ops_1 = require("./ops");
const attributionCollection_1 = require("./attributionCollection");
function createAttributionPolicyFromCallbacks({ delta, maintenance, }) {
    let unsubscribe;
    return {
        attach: (client) => {
            (0, common_utils_1.assert)(unsubscribe === undefined, 0x557 /* cannot attach to multiple clients at once */);
            const deltaSubscribed = (opArgs, deltaArgs) => delta(opArgs, deltaArgs, client);
            const maintenanceSubscribed = (args, opArgs) => maintenance(args, opArgs, client);
            client.on("delta", deltaSubscribed);
            client.on("maintenance", maintenanceSubscribed);
            unsubscribe = () => {
                client.off("delta", deltaSubscribed);
                client.off("maintenance", maintenanceSubscribed);
            };
        },
        detach: () => {
            unsubscribe === null || unsubscribe === void 0 ? void 0 : unsubscribe();
            unsubscribe = undefined;
        },
        get isAttached() {
            return unsubscribe !== undefined;
        },
        serializer: attributionCollection_1.AttributionCollection,
    };
}
const ensureAttributionCollectionCallbacks = {
    delta: ({ op }, { deltaSegments }) => {
        if (op.type === ops_1.MergeTreeDeltaType.INSERT) {
            for (const { segment } of deltaSegments) {
                segment.attribution = new attributionCollection_1.AttributionCollection(segment.cachedLength);
            }
        }
    },
    maintenance: () => { },
};
const getAttributionKey = (client, msg) => {
    if (msg) {
        return { type: "op", seq: msg.sequenceNumber };
    }
    const collabWindow = client.getCollabWindow();
    return collabWindow.collaborating ? { type: "local" } : { type: "detached", id: 0 };
};
const attributeInsertionOnSegments = (deltaSegments, key) => {
    var _a;
    for (const { segment } of deltaSegments) {
        if (segment.seq !== undefined) {
            (_a = segment.attribution) === null || _a === void 0 ? void 0 : _a.update(undefined, new attributionCollection_1.AttributionCollection(segment.cachedLength, key));
        }
    }
};
const insertOnlyAttributionPolicyCallbacks = {
    delta: (opArgs, { deltaSegments, operation }, client) => {
        if (operation === ops_1.MergeTreeDeltaType.INSERT) {
            attributeInsertionOnSegments(deltaSegments, getAttributionKey(client, opArgs.sequencedMessage));
        }
    },
    maintenance: ({ deltaSegments, operation }, opArgs, client) => {
        if (operation === mergeTreeDeltaCallback_1.MergeTreeMaintenanceType.ACKNOWLEDGED &&
            (opArgs === null || opArgs === void 0 ? void 0 : opArgs.op.type) === ops_1.MergeTreeDeltaType.INSERT) {
            attributeInsertionOnSegments(deltaSegments, getAttributionKey(client, opArgs.sequencedMessage));
        }
    },
};
function createPropertyTrackingMergeTreeCallbacks(...propNames) {
    const toTrack = propNames.map((entry) => ({ propName: entry, channelName: entry }));
    const attributeAnnotateOnSegments = (deltaSegments, { op, sequencedMessage }, key) => {
        var _a, _b, _c;
        for (const { segment } of deltaSegments) {
            for (const { propName, channelName } of toTrack) {
                const shouldAttributeInsert = op.type === ops_1.MergeTreeDeltaType.INSERT &&
                    ((_a = segment.properties) === null || _a === void 0 ? void 0 : _a[propName]) !== undefined;
                const isLocal = sequencedMessage === undefined;
                const shouldAttributeAnnotate = op.type === ops_1.MergeTreeDeltaType.ANNOTATE &&
                    // Only attribute annotations which change the tracked property
                    op.props[propName] !== undefined &&
                    // Local changes to the tracked property always take effect
                    (isLocal ||
                        // Acked changes only take effect if there isn't a pending local change
                        (!isLocal && !((_b = segment.propertyManager) === null || _b === void 0 ? void 0 : _b.hasPendingProperty(propName))));
                if (shouldAttributeInsert || shouldAttributeAnnotate) {
                    (_c = segment.attribution) === null || _c === void 0 ? void 0 : _c.update(channelName, new attributionCollection_1.AttributionCollection(segment.cachedLength, key));
                }
            }
        }
    };
    return {
        delta: (opArgs, { deltaSegments }, client) => {
            const { op, sequencedMessage } = opArgs;
            if (op.type === ops_1.MergeTreeDeltaType.ANNOTATE || op.type === ops_1.MergeTreeDeltaType.INSERT) {
                attributeAnnotateOnSegments(deltaSegments, opArgs, getAttributionKey(client, sequencedMessage));
            }
        },
        maintenance: ({ deltaSegments, operation }, opArgs, client) => {
            if (operation === mergeTreeDeltaCallback_1.MergeTreeMaintenanceType.ACKNOWLEDGED && opArgs !== undefined) {
                attributeAnnotateOnSegments(deltaSegments, opArgs, getAttributionKey(client, opArgs.sequencedMessage));
            }
        },
    };
}
function combineMergeTreeCallbacks(callbacks) {
    return {
        delta: (...args) => callbacks.forEach(({ delta }) => delta(...args)),
        maintenance: (...args) => callbacks.forEach(({ maintenance }) => maintenance(...args)),
    };
}
/**
 * @alpha
 * @returns - An {@link AttributionPolicy} which tracks only insertion of content.
 */
function createInsertOnlyAttributionPolicy() {
    return createAttributionPolicyFromCallbacks(combineMergeTreeCallbacks([
        ensureAttributionCollectionCallbacks,
        insertOnlyAttributionPolicyCallbacks,
    ]));
}
exports.createInsertOnlyAttributionPolicy = createInsertOnlyAttributionPolicy;
/**
 * @param propNames - List of property names for which attribution should be tracked.
 * @returns - A policy which only attributes annotation of the properties specified.
 * Keys for each property are stored under attribution channels of the same name--see example below.
 *
 * @example
 *
 * ```typescript
 * // Use this policy when creating your merge-tree:
 * const policy = createPropertyTrackingAttributionPolicyFactory("bold", "italic");
 * // ... later, you can get attribution keys for the last time the "bold" and "italic"
 * // properties were changed on a segment using `getAtOffset`:
 * const lastBoldedAttributionKey = segment.attribution?.getAtOffset(0, "bold");
 * const lastItalicizedAttributionKey = segment.attribution?.getAtOffset(0, "italic");
 * ```
 * @alpha
 */
function createPropertyTrackingAttributionPolicyFactory(...propNames) {
    return () => createAttributionPolicyFromCallbacks(combineMergeTreeCallbacks([
        ensureAttributionCollectionCallbacks,
        createPropertyTrackingMergeTreeCallbacks(...propNames),
    ]));
}
exports.createPropertyTrackingAttributionPolicyFactory = createPropertyTrackingAttributionPolicyFactory;
/**
 * Creates an attribution policy which tracks insertion as well as annotation of certain property names.
 * This combines the policies creatable using {@link createPropertyTrackingAttributionPolicyFactory} and
 * {@link createInsertOnlyAttributionPolicy}: see there for more details.
 * @alpha
 */
function createPropertyTrackingAndInsertionAttributionPolicyFactory(...propNames) {
    return () => createAttributionPolicyFromCallbacks(combineMergeTreeCallbacks([
        ensureAttributionCollectionCallbacks,
        insertOnlyAttributionPolicyCallbacks,
        createPropertyTrackingMergeTreeCallbacks(...propNames),
    ]));
}
exports.createPropertyTrackingAndInsertionAttributionPolicyFactory = createPropertyTrackingAndInsertionAttributionPolicyFactory;
//# sourceMappingURL=attributionPolicy.js.map