/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { strict as assert } from "node:assert";
import { Side } from "../sequencePlace.js";
import { annotateRange, removeRange } from "./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);
        assert(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);
        assert(Number.isInteger(Number(text)) || text === "{", "Non-integer character found within a field");
        endPos++;
    }
    assert(client.getText(startPos, startPos + 1) === "{", "Start separator not found");
    assert(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);
};
export const insertField = (client, opStart, opEnd, random) => {
    const numberText = generateFieldText(client, random);
    if (posInField(client, opStart) === undefined) {
        return client.insertTextLocal(opStart, `{${numberText}}`);
    }
};
const obliterateHelper = (client, startPos, endPos, random) => {
    const obliterateOp = client.obliterateRangeLocal({ pos: startPos, side: Side.After }, { pos: endPos, side: Side.Before });
    const insertOp = insertFieldText(client, startPos + 1, random);
    assert(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];
};
export 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.
        return endPos - startPos > 1
            ? obliterateHelper(client, startPos, endPos, random)
            : undefined;
    }
    if (opEnd >= client.getLength()) {
        endISP = { pos: client.getLength() - 1, side: 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: Side.Before }, endISP ?? { pos: opEnd, side: Side.After });
    }
};
export const obliterateFieldZeroLength = (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, including the case where the obliterate range is zero length.
        return obliterateHelper(client, startPos, endPos, random);
    }
    if (opEnd >= client.getLength()) {
        endISP = { pos: client.getLength() - 1, side: 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: Side.Before }, endISP ?? { pos: opEnd, side: Side.After });
    }
};
export 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)));
};
export 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 removeRange(client, start, end, random);
};
export 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 annotateRange(client, start, end, random);
};
export 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);
};
//# sourceMappingURL=obliterateOperations.js.map