import { Effect } from 'effect';
import { ValueTag } from '@bilig/protocol';
import { createBatch } from '../../replica-state.js';
import { cellMutationRefToEngineOp, cloneCellMutationRef, countPotentialNewCellsForMutationRefs, } from '../../cell-mutations-at.js';
import { EngineMutationError } from '../errors.js';
import { tryBuildFastMutationHistory } from './mutation-history-fast-path.js';
import { cloneTransactionRecordOps, createExistingNumericCellMutationsTransactionRecord, createLazyReversedExistingNumericCellMutationsTransactionRecord, createLazyCellMutationTransactionRecord, createLazySingleOpTransactionRecord, createOpsTransactionRecord, existingNumericCellMutationsRecordToRefs, createSingleExistingLiteralCellMutationTransactionRecord, createSingleExistingNumericCellMutationTransactionRecord, singleExistingLiteralCellMutationRecordToRef, singleExistingNumericCellMutationRecordToRef, transactionRecordOps, } from './mutation-transaction-records.js';
import { normalizeRenderCommitOps } from './mutation-render-commit-normalizer.js';
import { inverseMutationStructuralInsertOp, isMutationStructuralInsertOp } from './mutation-cell-content-helpers.js';
import { createMutationCellRestoreHistoryHelpers, tryMutationCellRefsFromOps } from './mutation-cell-restore-history.js';
import { createMutationStructuralDeleteInverseHelpers } from './mutation-structural-delete-inverse.js';
import { tryExecuteMutationRenderCommitFastPath } from './mutation-render-commit-fast-path.js';
import { createMutationRangeOperations } from './mutation-range-operations.js';
import { createMutationCoreInverseOps } from './mutation-core-inverse-ops.js';
export function createEngineMutationService(args) {
    const emptyBatchOps = [];
    const shouldCreateLocalBatch = () => args.state.trackReplicaVersions ||
        (args.state.batchListeners?.size ?? 0) > 0 ||
        (args.state.getSyncClientConnection?.() ?? null) !== null;
    const hasExternallyVisibleBatchRequirement = () => args.hasExternallyVisibleLocalMutationObservers?.() ??
        ((args.state.batchListeners?.size ?? 0) > 0 || (args.state.getSyncClientConnection?.() ?? null) !== null);
    const { restoreCellOpFromRef, tryRestoreSimpleCellOpFromStore, createLazyInverseCellMutationRecord, tryCreateSingleExistingNumericInverseCellMutationRecord, tryCreateExistingNumericInverseCellMutationRecord, tryCreateExistingNumericMutationHistoryRecords, buildFastMutationHistoryFromRefs, } = createMutationCellRestoreHistoryHelpers({
        workbook: args.state.workbook,
        formulas: args.state.formulas,
        getCellByIndex: args.getCellByIndex,
        ...(args.getFormulaFamilyStructuralSourceTransform
            ? { getFormulaFamilyStructuralSourceTransform: args.getFormulaFamilyStructuralSourceTransform }
            : {}),
        ...(args.hasFormulaFamilyStructuralSourceTransforms
            ? { hasFormulaFamilyStructuralSourceTransforms: args.hasFormulaFamilyStructuralSourceTransforms }
            : {}),
    });
    const { captureFormulaCellStateForStructuralUndo, captureFormulaCellStateForStructuralInsertUndo, buildStructuralDeleteInverseRecord } = createMutationStructuralDeleteInverseHelpers({
        state: args.state,
        getCellByIndex: args.getCellByIndex,
        toCellStateOps: (sheetName, address, snapshot) => args.toCellStateOps(sheetName, address, snapshot),
        ...(args.getFormulaFamilyStructuralSourceTransform
            ? { getFormulaFamilyStructuralSourceTransform: args.getFormulaFamilyStructuralSourceTransform }
            : {}),
        ...(args.hasFormulaFamilyStructuralSourceTransforms
            ? { hasFormulaFamilyStructuralSourceTransforms: args.hasFormulaFamilyStructuralSourceTransforms }
            : {}),
    });
    const { buildInverseOps } = createMutationCoreInverseOps({
        workbook: args.state.workbook,
        captureSheetCellState: args.captureSheetCellState,
        captureRowRangeCellState: args.captureRowRangeCellState,
        captureColumnRangeCellState: args.captureColumnRangeCellState,
        restoreCellOps: args.restoreCellOps,
        captureFormulaCellStateForStructuralUndo,
    });
    const tryBuildSingleCellOpHistoryWithoutSnapshot = (ops, potentialNewCells, includeUndoOps, cloneForwardOp) => {
        if (ops.length !== 1) {
            return null;
        }
        const op = ops[0];
        if (op.kind !== 'setCellValue' && op.kind !== 'setCellFormula' && op.kind !== 'clearCell') {
            return null;
        }
        const inverseOp = tryRestoreSimpleCellOpFromStore(op.sheetName, op.address);
        if (inverseOp === null) {
            return null;
        }
        const forwardOp = cloneForwardOp ? structuredClone(op) : op;
        return {
            forward: createLazySingleOpTransactionRecord(forwardOp, potentialNewCells),
            inverse: createLazySingleOpTransactionRecord(inverseOp, 1),
            undoOps: includeUndoOps ? [structuredClone(inverseOp)] : null,
        };
    };
    const tryCellMutationRefsFromOps = (ops) => tryMutationCellRefsFromOps(args.state.workbook, ops);
    const buildInverseRecord = (ops, includeStandaloneFormulaUndoOps) => {
        if (ops.length === 1 && (ops[0]?.kind === 'deleteRows' || ops[0]?.kind === 'deleteColumns')) {
            return buildStructuralDeleteInverseRecord(ops[0], { includeStandaloneFormulaUndoOps });
        }
        return { kind: 'ops', ops: buildInverseOps(ops), potentialNewCells: ops.length };
    };
    const canonicalizeForwardOps = (ops) => ops.map((op) => {
        if (op.kind === 'insertRows') {
            return op.entries
                ? { ...op, entries: op.entries.map((entry) => ({ ...entry })) }
                : {
                    ...op,
                    entries: args.state.workbook.snapshotRowAxisEntries(op.sheetName, op.start, op.count),
                };
        }
        if (op.kind === 'insertColumns') {
            return op.entries
                ? { ...op, entries: op.entries.map((entry) => ({ ...entry })) }
                : {
                    ...op,
                    entries: args.state.workbook.snapshotColumnAxisEntries(op.sheetName, op.start, op.count),
                };
        }
        return structuredClone(op);
    });
    const canonicalizeStructuralInsertForwardOp = (op) => {
        if (op.kind === 'insertRows') {
            const entries = args.state.workbook.snapshotRowAxisEntries(op.sheetName, op.start, op.count);
            return { ...op, entries };
        }
        const entries = args.state.workbook.snapshotColumnAxisEntries(op.sheetName, op.start, op.count);
        return { ...op, entries };
    };
    const executeTransactionNow = (record, source, options = {}) => {
        if ((record.kind === 'ops' && record.ops.length === 0) ||
            (record.kind === 'cell-mutations' && record.refs.length === 0) ||
            (record.kind === 'existing-numeric-cell-mutations' && record.sheetIds.length === 0)) {
            return;
        }
        if (record.kind === 'single-existing-numeric-cell-mutation') {
            const ref = singleExistingNumericCellMutationRecordToRef(record);
            const refs = [ref];
            const batch = shouldCreateLocalBatch()
                ? createBatch(args.state.replicaState, [cellMutationRefToEngineOp(args.state.workbook, ref)])
                : null;
            args.applyCellMutationsAtBatchNow(refs, batch, source, record.potentialNewCells);
            return;
        }
        if (record.kind === 'single-existing-literal-cell-mutation') {
            const ref = singleExistingLiteralCellMutationRecordToRef(record);
            const refs = [ref];
            const batch = shouldCreateLocalBatch()
                ? createBatch(args.state.replicaState, [cellMutationRefToEngineOp(args.state.workbook, ref)])
                : null;
            args.applyCellMutationsAtBatchNow(refs, batch, source, record.potentialNewCells);
            return;
        }
        if (record.kind === 'cell-mutations') {
            const batch = shouldCreateLocalBatch()
                ? createBatch(args.state.replicaState, record.refs.map((ref) => cellMutationRefToEngineOp(args.state.workbook, ref)))
                : null;
            args.applyCellMutationsAtBatchNow(record.refs, batch, source, record.potentialNewCells);
            return;
        }
        if (record.kind === 'existing-numeric-cell-mutations') {
            const refs = shouldCreateLocalBatch() ? existingNumericCellMutationsRecordToRefs(record) : undefined;
            const batch = refs === undefined
                ? null
                : createBatch(args.state.replicaState, refs.map((ref) => cellMutationRefToEngineOp(args.state.workbook, ref)));
            if (args.applyExistingNumericCellMutationsAtBatchNow?.(record, batch, source)) {
                return;
            }
            const materializedRefs = refs ?? existingNumericCellMutationsRecordToRefs(record);
            args.applyCellMutationsAtBatchNow(materializedRefs, batch, source, record.potentialNewCells);
            return;
        }
        if (record.kind === 'rename-sheet') {
            const batch = createBatch(args.state.replicaState, [{ kind: 'renameSheet', oldName: record.oldName, newName: record.newName }]);
            args.applyBatchNow(batch, source, undefined, undefined, options);
            return;
        }
        if (record.kind === 'single-op' &&
            source === 'local' &&
            options.emitTracked === false &&
            !shouldCreateLocalBatch() &&
            isMutationStructuralInsertOp(record.op) &&
            args.applyLocalSingleStructuralAxisOpWithoutBatchNow?.(record.op, options)) {
            return;
        }
        const batch = createBatch(args.state.replicaState, record.kind === 'single-op' ? [record.op] : record.ops);
        args.applyBatchNow(batch, source, record.potentialNewCells, record.kind === 'ops' ? record.preparedCellAddressesByOpIndex : undefined, options);
    };
    const executeLocalNowWithCustomApply = (ops, potentialNewCells, applyForward, options = {}) => {
        if (ops.length === 0) {
            return null;
        }
        if (options.returnUndoOps === false &&
            ops.length === 1 &&
            options.preparedCellAddressesByOpIndex === undefined &&
            isMutationStructuralInsertOp(ops[0])) {
            const op = ops[0];
            applyLocalStructuralInsertWithUndo(op, potentialNewCells, applyForward);
            return null;
        }
        if (options.returnUndoOps === false &&
            ops.length === 1 &&
            options.preparedCellAddressesByOpIndex === undefined &&
            ops[0]?.kind === 'renameSheet') {
            const op = ops[0];
            applyForward(potentialNewCells === undefined ? { kind: 'single-op', op } : { kind: 'single-op', op, potentialNewCells });
            if (args.state.getTransactionReplayDepth() === 0) {
                args.state.undoStack.push({
                    forward: createLazySingleOpTransactionRecord({ kind: 'renameSheet', oldName: op.oldName, newName: op.newName }, potentialNewCells),
                    inverse: createLazySingleOpTransactionRecord({ kind: 'renameSheet', oldName: op.newName, newName: op.oldName }),
                });
                args.state.redoStack.length = 0;
            }
            return null;
        }
        if (options.returnUndoOps === false &&
            ops.length === 1 &&
            options.preparedCellAddressesByOpIndex === undefined &&
            ops[0]?.kind === 'upsertDefinedName') {
            const op = ops[0];
            const existing = args.state.workbook.getDefinedName(op.name);
            const inverseOp = existing === undefined
                ? { kind: 'deleteDefinedName', name: op.name }
                : { kind: 'upsertDefinedName', name: existing.name, value: structuredClone(existing.value) };
            applyForward(potentialNewCells === undefined ? { kind: 'single-op', op } : { kind: 'single-op', op, potentialNewCells });
            if (args.state.getTransactionReplayDepth() === 0) {
                args.state.undoStack.push({
                    forward: createLazySingleOpTransactionRecord({
                        kind: 'upsertDefinedName',
                        name: op.name,
                        value: structuredClone(op.value),
                    }),
                    inverse: createLazySingleOpTransactionRecord(inverseOp),
                });
                args.state.redoStack.length = 0;
            }
            return null;
        }
        const forward = createOpsTransactionRecord(ops, potentialNewCells, options.preparedCellAddressesByOpIndex);
        const baseFastHistoryArgs = potentialNewCells === undefined
            ? {
                workbook: args.state.workbook,
                getCellByIndex: args.getCellByIndex,
                ops,
                cloneForwardOps: options.reuseForwardOps !== true,
            }
            : {
                workbook: args.state.workbook,
                getCellByIndex: args.getCellByIndex,
                ops,
                potentialNewCells,
                includeUndoOps: options.returnUndoOps !== false,
                cloneForwardOps: options.reuseForwardOps !== true,
            };
        const fastHistoryArgs = options.preparedCellAddressesByOpIndex
            ? {
                ...baseFastHistoryArgs,
                preparedCellAddressesByOpIndex: options.preparedCellAddressesByOpIndex,
            }
            : baseFastHistoryArgs;
        const fastHistory = tryBuildSingleCellOpHistoryWithoutSnapshot(ops, potentialNewCells, options.returnUndoOps !== false, options.reuseForwardOps !== true) ?? tryBuildFastMutationHistory(fastHistoryArgs);
        const inverse = fastHistory?.inverse ?? buildInverseRecord(ops, options.returnUndoOps !== false);
        applyForward(forward);
        if (args.state.getTransactionReplayDepth() === 0) {
            args.state.undoStack.push({
                forward: fastHistory?.forward ??
                    createOpsTransactionRecord(canonicalizeForwardOps(ops), potentialNewCells, options.preparedCellAddressesByOpIndex),
                inverse,
            });
            args.state.redoStack.length = 0;
        }
        if (options.returnUndoOps === false) {
            return null;
        }
        return fastHistory?.undoOps ?? cloneTransactionRecordOps(args.state.workbook, inverse);
    };
    const executeLocalCellMutationsAtNow = (refs, potentialNewCells, options = {}) => {
        if (refs.length === 0) {
            return null;
        }
        const nextRefs = options.reuseRefs ? refs : refs.map((ref) => cloneCellMutationRef(ref));
        const nextPotentialNewCells = potentialNewCells ?? countPotentialNewCellsForMutationRefs(nextRefs);
        const shouldCreateBatch = shouldCreateLocalBatch();
        if (options.returnUndoOps === false) {
            const typedNumericHistory = !shouldCreateBatch && nextPotentialNewCells === 0 ? tryCreateExistingNumericMutationHistoryRecords(nextRefs) : null;
            if (typedNumericHistory !== null &&
                typedNumericHistory.forward.kind === 'existing-numeric-cell-mutations' &&
                args.applyExistingNumericCellMutationsAtBatchNow?.(typedNumericHistory.forward, null, 'local')) {
                if (args.state.getTransactionReplayDepth() === 0) {
                    args.state.undoStack.push(typedNumericHistory);
                    args.state.redoStack.length = 0;
                }
                return null;
            }
            const inverse = tryCreateSingleExistingNumericInverseCellMutationRecord(nextRefs) ??
                tryCreateExistingNumericInverseCellMutationRecord(nextRefs) ??
                createLazyInverseCellMutationRecord(nextRefs);
            const batch = shouldCreateBatch
                ? createBatch(args.state.replicaState, nextRefs.map((ref) => cellMutationRefToEngineOp(args.state.workbook, ref)))
                : null;
            args.applyCellMutationsAtBatchNow(nextRefs, batch, 'local', nextPotentialNewCells);
            if (args.state.getTransactionReplayDepth() === 0) {
                args.state.undoStack.push({
                    forward: createLazyCellMutationTransactionRecord(nextRefs, nextPotentialNewCells),
                    inverse,
                });
                args.state.redoStack.length = 0;
            }
            return null;
        }
        const fastHistory = buildFastMutationHistoryFromRefs(nextRefs, nextPotentialNewCells);
        const inverse = fastHistory?.inverse ?? {
            kind: 'ops',
            ops: buildInverseOps(transactionRecordOps(args.state.workbook, fastHistory.forward)),
            potentialNewCells: transactionRecordOps(args.state.workbook, fastHistory.forward).length,
        };
        const batch = shouldCreateBatch
            ? createBatch(args.state.replicaState, [...transactionRecordOps(args.state.workbook, fastHistory.forward)])
            : null;
        args.applyCellMutationsAtBatchNow(nextRefs, batch, 'local', nextPotentialNewCells);
        if (args.state.getTransactionReplayDepth() === 0) {
            args.state.undoStack.push({
                forward: fastHistory?.forward ?? {
                    kind: 'ops',
                    ops: canonicalizeForwardOps([...transactionRecordOps(args.state.workbook, fastHistory.forward)]),
                    potentialNewCells: nextPotentialNewCells,
                },
                inverse,
            });
            args.state.redoStack.length = 0;
        }
        return fastHistory?.undoOps ?? cloneTransactionRecordOps(args.state.workbook, inverse);
    };
    const tryCreateExistingNumericMutationHistoryRecordsFromTypedBatch = (request) => {
        const count = request.sheetIds.length;
        if (count === 0 ||
            request.cellIndexPlusOnes.length !== count ||
            request.rows.length !== count ||
            request.cols.length !== count ||
            request.numbers.length !== count ||
            (request.potentialNewCells ?? 0) !== 0) {
            return null;
        }
        const oldNumbers = new Float64Array(count);
        const cellStore = args.state.workbook.cellStore;
        for (let index = 0; index < count; index += 1) {
            const cellIndexPlusOne = request.cellIndexPlusOnes[index];
            if (cellIndexPlusOne === 0) {
                return null;
            }
            const cellIndex = cellIndexPlusOne - 1;
            if (cellStore.sheetIds[cellIndex] !== request.sheetIds[index] ||
                cellStore.rows[cellIndex] !== request.rows[index] ||
                cellStore.cols[cellIndex] !== request.cols[index] ||
                (cellStore.formulaIds[cellIndex] ?? 0) !== 0 ||
                cellStore.tags[cellIndex] !== ValueTag.Number) {
                return null;
            }
            oldNumbers[index] = cellStore.numbers[cellIndex] ?? 0;
        }
        return {
            forward: createExistingNumericCellMutationsTransactionRecord({
                sheetIds: request.sheetIds,
                cellIndexPlusOnes: request.cellIndexPlusOnes,
                rows: request.rows,
                cols: request.cols,
                numbers: request.numbers,
            }, 0),
            inverse: createLazyReversedExistingNumericCellMutationsTransactionRecord({
                sheetIds: request.sheetIds,
                cellIndexPlusOnes: request.cellIndexPlusOnes,
                rows: request.rows,
                cols: request.cols,
            }, oldNumbers, 0),
        };
    };
    const executeLocalExistingNumericCellMutationsAtNow = (request, options = {}) => {
        if (options.returnUndoOps !== false || shouldCreateLocalBatch()) {
            return false;
        }
        const history = tryCreateExistingNumericMutationHistoryRecordsFromTypedBatch(request);
        if (!history) {
            return false;
        }
        if (args.applyExistingNumericCellMutationsAtBatchNow?.(history.forward, null, 'local') !== true) {
            args.applyCellMutationsAtBatchNow(existingNumericCellMutationsRecordToRefs(history.forward), null, 'local', 0);
        }
        if (args.state.getTransactionReplayDepth() === 0) {
            args.state.undoStack.push(history);
            args.state.redoStack.length = 0;
        }
        return true;
    };
    const executeLocalExistingNumericCellMutationAtNow = (request, options = {}) => {
        if (options.returnUndoOps !== false || shouldCreateLocalBatch()) {
            return null;
        }
        const cellStore = args.state.workbook.cellStore;
        const cellIndex = request.cellIndex;
        const oldNumericValue = request.trustedExistingNumericLiteral && request.oldNumericValue !== undefined
            ? request.oldNumericValue
            : (cellStore.numbers[cellIndex] ?? 0);
        const result = args.applyExistingNumericCellMutationAtNow?.(request);
        if (!result) {
            return null;
        }
        if (args.state.getTransactionReplayDepth() === 0) {
            args.state.undoStack.push({
                forward: createSingleExistingNumericCellMutationTransactionRecord(request, 0),
                inverse: createSingleExistingNumericCellMutationTransactionRecord({
                    sheetId: request.sheetId,
                    cellIndex,
                    row: request.row,
                    col: request.col,
                    value: oldNumericValue,
                }, 1),
            });
            args.state.redoStack.length = 0;
        }
        return result;
    };
    const executeLocalExistingLiteralCellMutationAtNow = (request, options = {}) => {
        if (typeof request.value === 'number') {
            return executeLocalExistingNumericCellMutationAtNow({
                sheetId: request.sheetId,
                row: request.row,
                col: request.col,
                cellIndex: request.cellIndex,
                value: request.value,
                ...(request.emitTracked === undefined ? {} : { emitTracked: request.emitTracked }),
            }, options);
        }
        if (options.returnUndoOps !== false || shouldCreateLocalBatch()) {
            return null;
        }
        const ref = {
            sheetId: request.sheetId,
            cellIndex: request.cellIndex,
            mutation: {
                kind: 'setCellValue',
                row: request.row,
                col: request.col,
                value: request.value,
            },
        };
        const inverse = createLazyInverseCellMutationRecord([ref]);
        const result = args.applyExistingLiteralCellMutationAtNow?.(request);
        if (!result) {
            return null;
        }
        if (args.state.getTransactionReplayDepth() === 0) {
            args.state.undoStack.push({
                forward: createSingleExistingLiteralCellMutationTransactionRecord(request, 0),
                inverse,
            });
            args.state.redoStack.length = 0;
        }
        return result;
    };
    const applyCellMutationsAtNow = (refs, options = {}) => {
        const source = options.source ?? 'restore';
        const captureUndo = options.captureUndo ?? source === 'local';
        if (captureUndo) {
            const executeOptions = {};
            if (options.returnUndoOps !== undefined) {
                executeOptions.returnUndoOps = options.returnUndoOps;
            }
            if (options.reuseRefs !== undefined) {
                executeOptions.reuseRefs = options.reuseRefs;
            }
            return executeLocalCellMutationsAtNow(refs, options.potentialNewCells, {
                ...executeOptions,
            });
        }
        if (refs.length === 0) {
            return null;
        }
        const nextRefs = options.reuseRefs ? refs : refs.map((ref) => cloneCellMutationRef(ref));
        const nextPotentialNewCells = options.potentialNewCells ?? countPotentialNewCellsForMutationRefs(nextRefs);
        const forwardOps = source === 'restore' ? emptyBatchOps : nextRefs.map((ref) => cellMutationRefToEngineOp(args.state.workbook, ref));
        const batch = source === 'local' && shouldCreateLocalBatch()
            ? createBatch(args.state.replicaState, forwardOps)
            : source === 'restore'
                ? null
                : createBatch(args.state.replicaState, forwardOps);
        args.applyCellMutationsAtBatchNow(nextRefs, batch, source, nextPotentialNewCells);
        return null;
    };
    const executeLocalNowPublic = (ops, potentialNewCells, options = {}) => {
        if (!shouldCreateLocalBatch()) {
            const refs = tryCellMutationRefsFromOps(ops);
            if (refs !== null) {
                return executeLocalCellMutationsAtNow(refs, potentialNewCells, {
                    returnUndoOps: options.returnUndoOps ?? true,
                    reuseRefs: true,
                });
            }
        }
        return executeLocalNowWithCustomApply(ops, potentialNewCells, (forward) => {
            executeTransactionNow(forward, 'local', options.emitTracked === undefined ? {} : { emitTracked: options.emitTracked });
        }, { returnUndoOps: options.returnUndoOps ?? true, reuseForwardOps: false });
    };
    const applyLocalStructuralInsertWithUndo = (op, potentialNewCells, applyForward, options = {}) => {
        const inverseStructuralDeleteOp = inverseMutationStructuralInsertOp(op);
        const formulaUndoOps = args.state.formulas.size === 0
            ? []
            : captureFormulaCellStateForStructuralInsertUndo(op.sheetName, op.kind === 'insertRows' ? 'row' : 'column', op.start, op.count);
        applyForward(potentialNewCells === undefined ? { kind: 'single-op', op } : { kind: 'single-op', op, potentialNewCells });
        if (options.recordHistory !== false && args.state.getTransactionReplayDepth() === 0) {
            args.state.undoStack.push({
                forward: createLazySingleOpTransactionRecord(canonicalizeStructuralInsertForwardOp(op), potentialNewCells),
                inverse: formulaUndoOps.length === 0
                    ? createLazySingleOpTransactionRecord(inverseStructuralDeleteOp)
                    : createOpsTransactionRecord([inverseStructuralDeleteOp, ...formulaUndoOps], formulaUndoOps.length),
            });
            args.state.redoStack.length = 0;
        }
    };
    const executeLocalSingleStructuralInsertNow = (op, potentialNewCells, options = {}) => {
        applyLocalStructuralInsertWithUndo(op, potentialNewCells, (forward) => {
            executeTransactionNow(forward, 'local', options.emitTracked === undefined ? {} : { emitTracked: options.emitTracked });
        }, options);
        return null;
    };
    const executeLocal = (ops, potentialNewCells, options = {}) => Effect.try({
        try: () => executeLocalNowPublic(ops, potentialNewCells, options),
        catch: (cause) => new EngineMutationError({
            message: 'Failed to execute local transaction',
            cause,
        }),
    });
    const rangeOperations = createMutationRangeOperations({
        workbook: args.state.workbook,
        getCellByIndex: args.getCellByIndex,
        readRangeCells: args.readRangeCells,
        toCellStateOps: args.toCellStateOps,
        executeLocal,
    });
    return {
        executeTransactionNow: executeTransactionNow,
        executeTransaction(record, source) {
            return Effect.try({
                try: () => {
                    executeTransactionNow(record, source);
                },
                catch: (cause) => new EngineMutationError({
                    message: `Failed to execute ${source} transaction`,
                    cause,
                }),
            });
        },
        executeLocalNow: executeLocalNowPublic,
        executeLocalSingleStructuralInsertNow,
        executeLocalCellMutationsAtNow(refs, potentialNewCells) {
            return executeLocalCellMutationsAtNow(refs, potentialNewCells);
        },
        executeLocalExistingNumericCellMutationAtNow(request, options = {}) {
            return executeLocalExistingNumericCellMutationAtNow(request, options);
        },
        executeLocalExistingNumericCellMutationsAtNow(request, options = {}) {
            return executeLocalExistingNumericCellMutationsAtNow(request, options);
        },
        executeLocalExistingLiteralCellMutationAtNow(request, options = {}) {
            return executeLocalExistingLiteralCellMutationAtNow(request, options);
        },
        applyCellMutationsAtNow(refs, options = {}) {
            return applyCellMutationsAtNow(refs, options);
        },
        applyCellMutationsAt(refs, options = {}) {
            return Effect.try({
                try: () => applyCellMutationsAtNow(refs, options),
                catch: (cause) => new EngineMutationError({
                    message: 'Failed to apply cell mutations',
                    cause,
                }),
            });
        },
        executeLocal,
        applyOpsNow(ops, options = {}) {
            const nextOps = options.trusted ? Array.from(ops) : structuredClone([...ops]);
            if (nextOps.length === 0) {
                return null;
            }
            if (options.captureUndo) {
                return options.returnUndoOps === undefined
                    ? this.executeLocalNow(nextOps, options.potentialNewCells)
                    : this.executeLocalNow(nextOps, options.potentialNewCells, { returnUndoOps: options.returnUndoOps });
            }
            executeTransactionNow(options.potentialNewCells === undefined
                ? { kind: 'ops', ops: nextOps }
                : { kind: 'ops', ops: nextOps, potentialNewCells: options.potentialNewCells }, options.source ?? 'restore');
            return null;
        },
        applyOps(ops, options = {}) {
            return Effect.try({
                try: () => this.applyOpsNow(ops, options),
                catch: (cause) => new EngineMutationError({
                    message: 'Failed to apply engine operations',
                    cause,
                }),
            });
        },
        captureUndoOps(mutate) {
            return Effect.try({
                try: () => {
                    const previousUndoDepth = args.state.undoStack.length;
                    const result = mutate();
                    if (args.state.undoStack.length === previousUndoDepth) {
                        return {
                            result,
                            undoOps: null,
                        };
                    }
                    if (args.state.undoStack.length === previousUndoDepth + 1) {
                        const inverse = args.state.undoStack.at(-1).inverse;
                        return {
                            result,
                            undoOps: cloneTransactionRecordOps(args.state.workbook, inverse),
                        };
                    }
                    throw new Error('Expected a single local transaction while capturing undo ops');
                },
                catch: (cause) => new EngineMutationError({
                    message: 'Failed to capture undo ops',
                    cause,
                }),
            });
        },
        setRangeValues: rangeOperations.setRangeValues,
        setRangeFormulas: rangeOperations.setRangeFormulas,
        clearRange: rangeOperations.clearRange,
        fillRange: rangeOperations.fillRange,
        copyRange: rangeOperations.copyRange,
        moveRange: rangeOperations.moveRange,
        importSheetCsv: rangeOperations.importSheetCsv,
        renderCommit(ops) {
            return Effect.flatMap(Effect.try({
                try: () => {
                    if (tryExecuteMutationRenderCommitFastPath({
                        state: args.state,
                        ops,
                        hasExternallyVisibleBatchRequirement,
                        restoreCellOpFromRef,
                        executeLocalNowWithCustomApply,
                        executeTransactionNow,
                        applyCellMutationsAtNow,
                    })) {
                        return null;
                    }
                    return normalizeRenderCommitOps(ops);
                },
                catch: (cause) => new EngineMutationError({
                    message: 'Failed to normalize render commit operations',
                    cause,
                }),
            }), (normalized) => {
                if (normalized === null) {
                    return Effect.void;
                }
                const { engineOps, potentialNewCells, preparedCellAddressesByOpIndex } = normalized;
                return Effect.try({
                    try: () => {
                        executeLocalNowWithCustomApply(engineOps, potentialNewCells, (forward) => {
                            const batchOps = [...transactionRecordOps(args.state.workbook, forward)];
                            args.applyBatchNow(createBatch(args.state.replicaState, batchOps), 'local', forward.potentialNewCells, preparedCellAddressesByOpIndex);
                        }, {
                            returnUndoOps: false,
                            reuseForwardOps: true,
                            preparedCellAddressesByOpIndex,
                        });
                    },
                    catch: (cause) => new EngineMutationError({
                        message: 'Failed to execute render commit transaction',
                        cause,
                    }),
                });
            }).pipe(Effect.asVoid);
        },
    };
}
//# sourceMappingURL=mutation-service.js.map