/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert } from "@fluidframework/core-utils/internal";
import { AttributionCollection } from "./attributionCollection.js";
import { MergeTreeMaintenanceType, } from "./mergeTreeDeltaCallback.js";
import { MergeTreeDeltaType } from "./ops.js";
import { isInserted } from "./segmentInfos.js";
function createAttributionPolicyFromCallbacks({ delta, maintenance, }) {
    let unsubscribe;
    return {
        attach: (client) => {
            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?.();
            unsubscribe = undefined;
        },
        get isAttached() {
            return unsubscribe !== undefined;
        },
        serializer: AttributionCollection,
    };
}
const ensureAttributionCollectionCallbacks = {
    delta: ({ op }, { deltaSegments }) => {
        if (op.type === MergeTreeDeltaType.INSERT) {
            for (const { segment } of deltaSegments) {
                segment.attribution = new 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) => {
    for (const { segment } of deltaSegments) {
        if (isInserted(segment)) {
            segment.attribution?.update(undefined, new AttributionCollection(segment.cachedLength, key));
        }
    }
};
const insertOnlyAttributionPolicyCallbacks = {
    delta: (opArgs, { deltaSegments, operation }, client) => {
        if (operation === MergeTreeDeltaType.INSERT) {
            attributeInsertionOnSegments(deltaSegments, getAttributionKey(client, opArgs.sequencedMessage));
        }
    },
    maintenance: ({ deltaSegments, operation }, opArgs, client) => {
        if (operation === MergeTreeMaintenanceType.ACKNOWLEDGED &&
            opArgs?.op.type === MergeTreeDeltaType.INSERT) {
            attributeInsertionOnSegments(deltaSegments, getAttributionKey(client, opArgs.sequencedMessage));
        }
    },
};
function createPropertyTrackingMergeTreeCallbacks(...propNames) {
    const toTrack = propNames.map((entry) => ({ propName: entry, channelName: entry }));
    const attributeAnnotateOnSegments = (isLocal, deltaSegments, { op }, key) => {
        for (const { segment, propertyDeltas } of deltaSegments) {
            for (const { propName, channelName } of toTrack) {
                const shouldAttributeInsert = op.type === MergeTreeDeltaType.INSERT &&
                    segment.properties?.[propName] !== undefined;
                const shouldAttributeAnnotate = op.type === MergeTreeDeltaType.ANNOTATE &&
                    // Only attribute annotations which change the tracked property
                    (op.props?.[propName] !== undefined || op.adjust?.[propName] !== undefined) &&
                    (isLocal || (propertyDeltas !== undefined && propName in propertyDeltas));
                if (shouldAttributeInsert || shouldAttributeAnnotate) {
                    segment.attribution?.update(channelName, new AttributionCollection(segment.cachedLength, key));
                }
            }
        }
    };
    return {
        delta: (opArgs, { deltaSegments }, client) => {
            const { op, sequencedMessage } = opArgs;
            if (op.type === MergeTreeDeltaType.ANNOTATE || op.type === MergeTreeDeltaType.INSERT) {
                attributeAnnotateOnSegments(sequencedMessage === undefined, deltaSegments, opArgs, getAttributionKey(client, sequencedMessage));
            }
        },
        maintenance: ({ deltaSegments, operation }, opArgs, client) => {
            if (operation === MergeTreeMaintenanceType.ACKNOWLEDGED && opArgs !== undefined) {
                attributeAnnotateOnSegments(true, deltaSegments, opArgs, getAttributionKey(client, opArgs.sequencedMessage));
            }
        },
    };
}
function combineMergeTreeCallbacks(callbacks) {
    return {
        delta: (...args) => {
            for (const { delta } of callbacks)
                delta(...args);
        },
        maintenance: (...args) => {
            for (const { maintenance } of callbacks)
                maintenance(...args);
        },
    };
}
/**
 * Creates an {@link AttributionPolicy} which only tracks initial insertion of content.
 * @internal
 */
export function createInsertOnlyAttributionPolicy() {
    return createAttributionPolicyFromCallbacks(combineMergeTreeCallbacks([
        ensureAttributionCollectionCallbacks,
        insertOnlyAttributionPolicyCallbacks,
    ]));
}
/**
 * Creates an {@link AttributionPolicy} for tracking annotation of specific properties.
 * @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");
 * ```
 * @internal
 */
export function createPropertyTrackingAttributionPolicyFactory(...propNames) {
    return () => createAttributionPolicyFromCallbacks(combineMergeTreeCallbacks([
        ensureAttributionCollectionCallbacks,
        createPropertyTrackingMergeTreeCallbacks(...propNames),
    ]));
}
/**
 * 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.
 * @internal
 */
export function createPropertyTrackingAndInsertionAttributionPolicyFactory(...propNames) {
    return () => createAttributionPolicyFromCallbacks(combineMergeTreeCallbacks([
        ensureAttributionCollectionCallbacks,
        insertOnlyAttributionPolicyCallbacks,
        createPropertyTrackingMergeTreeCallbacks(...propNames),
    ]));
}
//# sourceMappingURL=attributionPolicy.js.map