/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { unreachableCase } from "@fluidframework/common-utils";
import { fail } from "../../../util";
export function isAttach(mark) {
    return (isObjMark(mark) &&
        "type" in mark &&
        (mark.type === "Insert" ||
            mark.type === "MInsert" ||
            mark.type === "MoveIn" ||
            mark.type === "MMoveIn" ||
            mark.type === "Bounce" ||
            mark.type === "Intake"));
}
export function isReattach(mark) {
    return (isObjMark(mark) &&
        "type" in mark &&
        (mark.type === "Revive" ||
            mark.type === "MRevive" ||
            mark.type === "Return" ||
            mark.type === "MReturn"));
}
export function isTomb(mark) {
    return isObjMark(mark) && "type" in mark && mark.type === "Tomb";
}
export function isGapEffectMark(mark) {
    return isObjMark(mark) && "type" in mark && mark.type === "Gap";
}
export function getAttachLength(attach) {
    const type = attach.type;
    switch (type) {
        case "Bounce":
        case "Intake":
            return 0;
        case "MInsert":
        case "MMoveIn":
            return 1;
        case "Insert":
            return attach.content.length;
        case "MoveIn":
            return attach.count;
        default:
            unreachableCase(type);
    }
}
/**
 * @returns `true` iff `lhs` and `rhs` are deeply structurally equal.
 */
export function isEqualGaps(lhs, rhs) {
    if (lhs === rhs) {
        return true;
    }
    if (lhs === undefined || rhs === undefined || lhs.length !== rhs.length) {
        return false;
    }
    for (let i = 0; i < lhs.length; ++i) {
        if (!isEqualGapEffect(lhs[i], rhs[i])) {
            return false;
        }
    }
    return true;
}
/**
 * @returns `true` iff `lhs` and `rhs`'s `HasPlaceFields` fields are structurally equal.
 */
export function isEqualPlace(lhs, rhs) {
    var _a, _b, _c, _d, _e, _f, _g, _h;
    return (lhs.heed === rhs.heed &&
        lhs.tiebreak === rhs.tiebreak &&
        ((_a = lhs.src) === null || _a === void 0 ? void 0 : _a.id) === ((_b = rhs.src) === null || _b === void 0 ? void 0 : _b.id) &&
        ((_c = lhs.src) === null || _c === void 0 ? void 0 : _c.change) === ((_d = rhs.src) === null || _d === void 0 ? void 0 : _d.change) &&
        ((_e = lhs.scorch) === null || _e === void 0 ? void 0 : _e.id) === ((_f = rhs.scorch) === null || _f === void 0 ? void 0 : _f.id) &&
        ((_g = lhs.scorch) === null || _g === void 0 ? void 0 : _g.change) === ((_h = rhs.scorch) === null || _h === void 0 ? void 0 : _h.change));
}
export function isEqualGapEffect(lhs, rhs) {
    return (lhs.id === rhs.id &&
        lhs.type === rhs.type &&
        lhs.excludePriorInsertions === rhs.excludePriorInsertions &&
        lhs.includePosteriorInsertions === rhs.includePosteriorInsertions);
}
/**
 * @param mark - The mark to get the length of.
 * @returns The number of nodes within the output context of the mark.
 */
export function getOutputLength(mark) {
    if (isSkipMark(mark)) {
        return mark;
    }
    const type = mark.type;
    switch (type) {
        case "Tomb":
        case "Gap":
        case "Revive":
        case "Return":
        case "MoveIn":
            return mark.count;
        case "Insert":
            return mark.content.length;
        case "MInsert":
        case "MMoveIn":
        case "MReturn":
        case "MRevive":
        case "Modify":
            return 1;
        case "Delete":
        case "MDelete":
        case "MoveOut":
        case "MMoveOut":
        case "Intake":
        case "Bounce":
            return 0;
        default:
            unreachableCase(type);
    }
}
/**
 * @param mark - The mark to get the length of.
 * @returns The number of nodes within the input context of the mark.
 */
export function getInputLength(mark) {
    if (isSkipMark(mark)) {
        return mark;
    }
    if (isAttach(mark)) {
        return 0;
    }
    const type = mark.type;
    switch (type) {
        case "Tomb":
        case "Gap":
        case "Revive":
        case "Return":
        case "Delete":
        case "MoveOut":
            return mark.count;
        case "MReturn":
        case "MRevive":
        case "Modify":
        case "MDelete":
        case "MMoveOut":
            return 1;
        default:
            unreachableCase(type);
    }
}
export function isSkipMark(mark) {
    return typeof mark === "number";
}
/**
 * Splits the `mark` into two marks such that the first returned mark has input length `length`.
 * @param mark - The mark to split.
 * @param length - The desired length for the first of the two returned marks.
 * @returns A pair of marks equivalent to the original `mark`
 * such that the first returned mark has input length `length`.
 */
export function splitMarkOnInput(mark, length) {
    const markLength = getInputLength(mark);
    const remainder = markLength - length;
    if (length < 1 || remainder < 1) {
        fail("Unable to split mark due to lengths");
    }
    if (isSkipMark(mark)) {
        return [length, remainder];
    }
    // The compiler seems to think the case below is necessary
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
    const markObj = mark;
    const type = markObj.type;
    switch (type) {
        case "Modify":
        case "MDelete":
        case "MMoveOut":
        case "MReturn":
        case "MRevive":
            fail("Unable to split mark of length 1");
        case "Delete":
        case "MoveOut":
        case "Return":
        case "Revive":
        case "Tomb":
        case "Gap":
            return [
                Object.assign(Object.assign({}, markObj), { count: length }),
                Object.assign(Object.assign({}, markObj), { count: remainder }),
            ];
        default:
            unreachableCase(type);
    }
}
/**
 * Splits the `mark` into two marks such that the first returned mark has output length `length`.
 * @param mark - The mark to split.
 * @param length - The desired length for the first of the two returned marks.
 * @returns A pair of marks equivalent to the original `mark`
 * such that the first returned mark has output length `length`.
 */
export function splitMarkOnOutput(mark, length) {
    const markLength = getOutputLength(mark);
    const remainder = markLength - length;
    if (length < 1 || remainder < 1) {
        fail("Unable to split mark due to lengths");
    }
    if (isSkipMark(mark)) {
        return [length, remainder];
    }
    const markObj = mark;
    const type = markObj.type;
    switch (type) {
        case "Modify":
        case "MReturn":
        case "MRevive":
        case "MInsert":
        case "MMoveIn":
            fail("Unable to split mark of length 1");
        case "MDelete":
        case "MMoveOut":
        case "Delete":
        case "MoveOut":
        case "Bounce":
        case "Intake":
            fail("Unable to split mark of length 0");
        case "Insert":
            return [
                Object.assign(Object.assign({}, markObj), { content: markObj.content.slice(0, length) }),
                Object.assign(Object.assign({}, markObj), { content: markObj.content.slice(length) }),
            ];
        case "MoveIn":
        case "Return":
        case "Revive":
        case "Tomb":
        case "Gap":
            return [
                Object.assign(Object.assign({}, markObj), { count: length }),
                Object.assign(Object.assign({}, markObj), { count: remainder }),
            ];
        default:
            unreachableCase(type);
    }
}
export function isDetachMark(mark) {
    if (isObjMark(mark) && "type" in mark) {
        const type = mark.type;
        return type === "Delete" || type === "MDelete" || type === "MoveOut" || type === "MMoveOut";
    }
    return false;
}
export function isObjMark(mark) {
    return typeof mark === "object";
}
/**
 * Attempts to extend `lhs` to include the effects of `rhs`.
 * @param lhs - The mark to extend.
 * @param rhs - The effect so extend `rhs` with.
 * @returns `true` iff the function was able to mutate `lhs` to include the effects of `rhs`.
 * When `false` is returned, `lhs` is left untouched.
 */
export function tryExtendMark(lhs, rhs) {
    var _a;
    if (rhs.type !== lhs.type) {
        return false;
    }
    const type = rhs.type;
    switch (type) {
        case "Insert":
        case "MoveIn": {
            const lhsAttach = lhs;
            if ((_a = rhs.id === lhsAttach.id) !== null && _a !== void 0 ? _a : isEqualPlace(lhsAttach, rhs)) {
                if (rhs.type === "Insert") {
                    const lhsInsert = lhsAttach;
                    lhsInsert.content.push(...rhs.content);
                }
                else {
                    const lhsMoveIn = lhsAttach;
                    lhsMoveIn.count += rhs.count;
                }
                return true;
            }
            break;
        }
        case "Delete":
        case "MoveOut": {
            const lhsDetach = lhs;
            if (rhs.id === lhsDetach.id &&
                rhs.tomb === lhsDetach.tomb &&
                isEqualGaps(rhs.gaps, lhsDetach.gaps)) {
                lhsDetach.count += rhs.count;
                return true;
            }
            break;
        }
        case "Revive":
        case "Return": {
            const lhsReattach = lhs;
            if (rhs.id === lhsReattach.id && rhs.tomb === lhsReattach.tomb) {
                lhsReattach.count += rhs.count;
                return true;
            }
            break;
        }
        case "Gap": {
            const lhsGap = lhs;
            if (rhs.tomb === lhsGap.tomb && isEqualGaps(rhs.stack, lhsGap.stack)) {
                lhsGap.count += rhs.count;
                return true;
            }
            break;
        }
        case "Tomb": {
            const lhsTomb = lhs;
            if (rhs.change === lhsTomb.change) {
                lhsTomb.count += rhs.count;
                return true;
            }
            break;
        }
        default:
            break;
    }
    return false;
}
//# sourceMappingURL=utils.js.map