"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateInsertWithField = exports.annotateWithField = exports.removeWithField = exports.insertAvoidField = exports.obliterateField = exports.insertField = void 0;
/* eslint-disable @typescript-eslint/no-non-null-assertion */
const node_assert_1 = require("node:assert");
const sequencePlace_js_1 = require("../sequencePlace.js");
const mergeTreeOperationRunner_js_1 = require("./mergeTreeOperationRunner.js");
const posInField = (client, pos) => {
    const isFieldCharacter = (char) => Number.isInteger(Number(char)) || char === "{" || char === "}";
    if (pos >= client.getLength() || !isFieldCharacter(client.getText(pos, pos + 1))) {
        // pos is not within a field.
        return undefined;
    }
    let startPos = pos;
    let endPos = pos;
    // To find the start and end separators, walk backwards and forwards until the desired character is found.
    while (startPos > 0 && client.getText(startPos, startPos + 1) !== "{") {
        const text = client.getText(endPos, endPos + 1);
        (0, node_assert_1.strict)(Number.isInteger(Number(text)) || text === "}", "Non-integer character found within a field");
        startPos--;
    }
    while (endPos < client.getLength() && client.getText(endPos, endPos + 1) !== "}") {
        const text = client.getText(endPos, endPos + 1);
        (0, node_assert_1.strict)(Number.isInteger(Number(text)) || text === "{", "Non-integer character found within a field");
        endPos++;
    }
    (0, node_assert_1.strict)(client.getText(startPos, startPos + 1) === "{", "Start separator not found");
    (0, node_assert_1.strict)(endPos < client.getLength() && client.getText(endPos, endPos + 1) === "}", "End separator not found");
    return { startPos, endPos };
};
const getFieldEndpoints = (client, start, end) => {
    const startField = posInField(client, start);
    const endField = posInField(client, end);
    if (startField === undefined && endField === undefined) {
        return undefined;
    }
    return startField ?? endField;
};
const generateFieldText = (client, random) => {
    const chunkLength = random.integer(1, 10);
    return (client.longClientId.codePointAt(0) % 10).toString().repeat(chunkLength);
};
const insertFieldText = (client, opStart, random) => {
    const text = generateFieldText(client, random);
    return client.insertTextLocal(opStart, text);
};
const insertField = (client, opStart, opEnd, random) => {
    const numberText = generateFieldText(client, random);
    if (posInField(client, opStart) === undefined) {
        return client.insertTextLocal(opStart, `{${numberText}}`);
    }
};
exports.insertField = insertField;
const obliterateField = (client, opStart, opEnd, random) => {
    const fieldEndpoints = getFieldEndpoints(client, opStart, 
    // the operation runner generates endpoints with client length, but this model only supports up to client length - 1.
    Math.min(opEnd, client.getLength() - 1));
    let endISP;
    if (fieldEndpoints !== undefined) {
        const { startPos, endPos } = fieldEndpoints;
        // Obliterate text between the separators, but avoid the case where the obliterate range is zero length.
        if (endPos - startPos > 1) {
            const obliterateOp = client.obliterateRangeLocal({ pos: startPos, side: sequencePlace_js_1.Side.After }, { pos: endPos, side: sequencePlace_js_1.Side.Before });
            const insertOp = insertFieldText(client, startPos + 1, random);
            (0, node_assert_1.strict)(insertOp !== undefined, "Insert op should not be undefined");
            // TODO: AB#31001: We should be able to sometimes use group ops here rather than submit two separate ops,
            // but this causes failures which likely indicate there are bugs with the intersection of obliterate and grouped batching.
            // const op = createGroupOp(obliterateOp, insertOp);
            return [obliterateOp, insertOp];
        }
        else {
            return;
        }
    }
    if (opEnd >= client.getLength()) {
        endISP = { pos: client.getLength() - 1, side: sequencePlace_js_1.Side.After };
    }
    if (!client.getText(opStart, opEnd).includes("{")) {
        // Avoid issuing obliterates that might contain multiple fields.
        // Otherwise we may end up with field characters that look like they're outside of the field,
        // since one of these obliterates can wipe out the field including the `{}` delimiters, but
        // a "field replace" obliterate + insert can win and insert the numerical characters.
        return client.obliterateRangeLocal({ pos: opStart, side: sequencePlace_js_1.Side.Before }, endISP ?? { pos: opEnd, side: sequencePlace_js_1.Side.After });
    }
};
exports.obliterateField = obliterateField;
const insertAvoidField = (client, opStart, opEnd, random) => {
    let start = opStart;
    const endpoints = posInField(client, opStart);
    if (endpoints !== undefined) {
        start = endpoints.startPos;
    }
    return client.insertTextLocal(start, client.longClientId.repeat(random.integer(1, 3)));
};
exports.insertAvoidField = insertAvoidField;
const removeWithField = (client, opStart, opEnd, random) => {
    let start = opStart;
    let end = opEnd;
    const fieldEndpoints = getFieldEndpoints(client, opStart, opEnd);
    if (fieldEndpoints !== undefined) {
        start = fieldEndpoints.startPos;
        end = fieldEndpoints.endPos + 1;
    }
    return (0, mergeTreeOperationRunner_js_1.removeRange)(client, start, end, random);
};
exports.removeWithField = removeWithField;
const annotateWithField = (client, opStart, opEnd, random) => {
    let start = opStart;
    let end = opEnd;
    const fieldEndpoints = getFieldEndpoints(client, opStart, opEnd);
    if (fieldEndpoints !== undefined) {
        start = fieldEndpoints.startPos;
        end = fieldEndpoints.endPos + 1;
    }
    return (0, mergeTreeOperationRunner_js_1.annotateRange)(client, start, end, random);
};
exports.annotateWithField = annotateWithField;
const generateInsertWithField = (client, random) => {
    const text = client.longClientId.repeat(random.integer(1, 3));
    let pos = random.integer(0, client.getLength());
    const endpoints = posInField(client, pos);
    if (endpoints !== undefined) {
        pos = 0;
    }
    return client.insertTextLocal(pos, text);
};
exports.generateInsertWithField = generateInsertWithField;
//# sourceMappingURL=obliterateOperations.js.map