import { cellMutationRefToEngineOp } from '../../cell-mutations-at.js';
import { batchOpOrder, markBatchApplied } from '../../replica-state.js';
import { DirectFormulaIndexCollection } from './direct-formula-index-collection.js';
import { aggregateColumnDependencyKey } from './direct-formula-recalc-helpers.js';
import { assertNever } from './operation-change-helpers.js';
import { createOperationTrackedColumnDependencyFlagResolver } from './operation-column-dependency-tracker.js';
import { createOperationSheetNameResolver, resolveOperationExistingMutationCellIndex } from './operation-cell-address-resolver.js';
import { finalizeOperationRecalcAndEvents } from './operation-recalc-finalizer.js';
import { createOperationFreshDirectAggregateFormulaBatchFastPath } from './operation-fresh-direct-aggregate-formula-batch-fast-path.js';
import { createOperationFreshDirectScalarFormulaBatchFastPath } from './operation-fresh-direct-scalar-formula-batch-fast-path.js';
import { createOperationExistingRectangularLiteralBatchFastPath } from './operation-existing-rectangular-literal-batch-fast-path.js';
import { applyClearCellMutation } from './operation-clear-cell-mutation.js';
import { applySetCellValueMutation } from './operation-set-cell-value-mutation.js';
import { applySetCellFormulaMutation } from './operation-set-cell-formula-mutation.js';
import { assertProtectionAllowsOp as assertProtectionAllowsProtectedOp } from './operation-protection-helpers.js';
function assertProtectionAllowsCellMutationRefs(workbook, refs) {
    const protectionBySheetId = new Map();
    for (const ref of refs) {
        const cachedProtectionSheetName = protectionBySheetId.get(ref.sheetId);
        if (cachedProtectionSheetName === null) {
            continue;
        }
        if (cachedProtectionSheetName !== undefined) {
            assertProtectionAllowsProtectedOp(workbook, cellMutationRefToEngineOp(workbook, ref));
            continue;
        }
        const sheet = workbook.getSheetById(ref.sheetId);
        if (!sheet) {
            throw new Error(`Unknown sheet id: ${ref.sheetId}`);
        }
        if (!workbook.hasProtectionMetadataForSheet(sheet.name)) {
            protectionBySheetId.set(ref.sheetId, null);
            continue;
        }
        protectionBySheetId.set(ref.sheetId, sheet.name);
        assertProtectionAllowsProtectedOp(workbook, cellMutationRefToEngineOp(workbook, ref));
    }
}
export function createOperationCellMutationApplier(input) {
    const { serviceArgs: args, emitBatch, setCellEntityVersion, isNullLiteralWriteNoOp, isClearCellNoOp, canFastPathLiteralOverwrite, tryApplySingleExistingDirectLiteralMutation, tryApplyCoalescedDirectScalarLiteralBatch, tryApplyFreshDenseRectangularNumericLiteralBatch, writeNumericLiteralToCellStore, readCellValueForLookup, readApproximateNumericValueForLookup, readExactNumericValueForLookup, hasTrackedExactLookupDependents, hasTrackedSortedLookupDependents, hasTrackedDirectRangeDependents, planExactLookupNumericColumnWrite, planApproximateLookupNumericColumnWrite, noteExactLookupLiteralWriteWhenDirty, noteSortedLookupLiteralWriteWhenDirty, markAffectedDirectRangeDependents, collectAffectedDirectRangeDependents, markPostRecalcDirectFormulaDependents, markDirectScalarDeltaClosure, markPostRecalcDirectScalarNumericDependents, markPostRecalcDirectLookupCurrentDependentsFromNumeric, rebindDynamicFormulaDependents, directScalarCellNumericValue, tryApplyFormulaReplacementAsDirectScalarDeltaRoot, markCycleMemberInputsChanged, hasCycleMembersNow, canSkipDirtyTraversalForChangedInputs, directFormulaCallbacks: { applyDirectFormulaCurrentResult, applyDirectFormulaNumericDelta, applyDirectScalarCurrentValue, tryApplyDirectScalarDeltas, tryApplyDirectFormulaDeltas, countPostRecalcDirectFormulaMetric, }, applyDirectFormulaNumericResult, pruneCellIfOrphaned, normalizeHistoryDependencyPlaceholder, } = input;
    let freshDirectAggregateFormulaBatchFastPath;
    const getFreshDirectAggregateFormulaBatchFastPath = () => {
        if (args.bindPreparedFormula === undefined || args.compileTemplateFormula === undefined) {
            return undefined;
        }
        return (freshDirectAggregateFormulaBatchFastPath ??= createOperationFreshDirectAggregateFormulaBatchFastPath({
            state: args.state,
            emitBatch,
            setCellEntityVersion,
            hasTrackedExactLookupDependents,
            hasTrackedSortedLookupDependents,
            hasTrackedDirectRangeDependents,
            ...(args.hasRegionFormulaSubscriptionsOverlappingRange === undefined
                ? {}
                : { hasRegionFormulaSubscriptionsOverlappingRange: args.hasRegionFormulaSubscriptionsOverlappingRange }),
            ...(args.getRegionFormulaSubscriptionCount === undefined
                ? {}
                : { getRegionFormulaSubscriptionCount: args.getRegionFormulaSubscriptionCount }),
            hasRegionFormulaSubscriptionsIntersectingRect: args.hasRegionFormulaSubscriptionsIntersectingRect,
            bindPreparedFormula: args.bindPreparedFormula,
            bindFreshDirectAggregateFormulaRun: args.bindFreshDirectAggregateFormulaRun,
            registerFreshFormulaFamilyRun: args.registerFreshFormulaFamilyRun,
            upsertFormulaFamilyRun: args.upsertFormulaFamilyRun,
            upsertFreshFormulaInstances: args.upsertFreshFormulaInstances,
            compileTemplateFormula: args.compileTemplateFormula,
            materializeDeferredStructuralFormulaSources: args.materializeDeferredStructuralFormulaSources,
            checkEvaluationBudget: args.checkEvaluationBudget,
            beginMutationCollection: args.beginMutationCollection,
            ensureRecalcScratchCapacity: args.ensureRecalcScratchCapacity,
            resetMaterializedCellScratch: args.resetMaterializedCellScratch,
            getBatchMutationDepth: args.getBatchMutationDepth,
            setBatchMutationDepth: args.setBatchMutationDepth,
            markInputChanged: args.markInputChanged,
            markExplicitChanged: args.markExplicitChanged,
            getChangedInputBuffer: args.getChangedInputBuffer,
            deferKernelSync: args.deferKernelSync,
            captureChangedCells: args.captureChangedCells,
            applyDirectFormulaCurrentResult,
            applyDirectFormulaNumericResult,
        }));
    };
    let freshDirectScalarFormulaBatchFastPath;
    const getFreshDirectScalarFormulaBatchFastPath = () => {
        if (args.bindFreshDirectScalarFormulaRun === undefined || args.compileTemplateFormula === undefined) {
            return undefined;
        }
        return (freshDirectScalarFormulaBatchFastPath ??= createOperationFreshDirectScalarFormulaBatchFastPath({
            state: args.state,
            emitBatch,
            setCellEntityVersion,
            hasTrackedExactLookupDependents,
            hasTrackedSortedLookupDependents,
            hasTrackedDirectRangeDependents,
            ...(args.hasRegionFormulaSubscriptionsOverlappingRange === undefined
                ? {}
                : { hasRegionFormulaSubscriptionsOverlappingRange: args.hasRegionFormulaSubscriptionsOverlappingRange }),
            ...(args.getRegionFormulaSubscriptionCount === undefined
                ? {}
                : { getRegionFormulaSubscriptionCount: args.getRegionFormulaSubscriptionCount }),
            hasRegionFormulaSubscriptionsIntersectingRect: args.hasRegionFormulaSubscriptionsIntersectingRect,
            bindFreshDirectScalarFormulaRun: args.bindFreshDirectScalarFormulaRun,
            registerFreshFormulaFamilyRun: args.registerFreshFormulaFamilyRun,
            upsertFormulaFamilyRun: args.upsertFormulaFamilyRun,
            upsertFreshFormulaInstances: args.upsertFreshFormulaInstances,
            compileTemplateFormula: args.compileTemplateFormula,
            materializeDeferredStructuralFormulaSources: args.materializeDeferredStructuralFormulaSources,
            checkEvaluationBudget: args.checkEvaluationBudget,
            beginMutationCollection: args.beginMutationCollection,
            ensureRecalcScratchCapacity: args.ensureRecalcScratchCapacity,
            resetMaterializedCellScratch: args.resetMaterializedCellScratch,
            getBatchMutationDepth: args.getBatchMutationDepth,
            setBatchMutationDepth: args.setBatchMutationDepth,
            markInputChanged: args.markInputChanged,
            markExplicitChanged: args.markExplicitChanged,
            getChangedInputBuffer: args.getChangedInputBuffer,
            deferKernelSync: args.deferKernelSync,
            captureChangedCells: args.captureChangedCells,
            applyDirectFormulaCurrentResult,
        }));
    };
    let existingRectangularLiteralBatchFastPath;
    const getExistingRectangularLiteralBatchFastPath = () => (existingRectangularLiteralBatchFastPath ??= createOperationExistingRectangularLiteralBatchFastPath({
        state: args.state,
        emitBatch,
        canFastPathLiteralOverwrite,
        writeNumericLiteralToCellStore,
        materializeDeferredStructuralFormulaSources: args.materializeDeferredStructuralFormulaSources,
        getBatchMutationDepth: args.getBatchMutationDepth,
        setBatchMutationDepth: args.setBatchMutationDepth,
        deferKernelSync: args.deferKernelSync,
        captureChangedCells: args.captureChangedCells,
    }));
    return function applyCellMutationsAtNow(refs, batch, source, potentialNewCells) {
        const isRestore = source === 'restore';
        if (!isRestore && source !== 'undo' && source !== 'redo') {
            assertProtectionAllowsCellMutationRefs(args.state.workbook, refs);
        }
        const hasLiteralMutations = refs.some((ref) => ref.mutation.kind === 'setCellValue' || ref.mutation.kind === 'clearCell');
        const hasFormulaMutations = refs.some((ref) => ref.mutation.kind === 'setCellFormula');
        if (hasLiteralMutations && tryApplySingleExistingDirectLiteralMutation(refs, batch, source)) {
            return;
        }
        const mayContainFreshCells = (potentialNewCells ?? refs.length) > 0;
        if (mayContainFreshCells && hasFormulaMutations) {
            if (getFreshDirectAggregateFormulaBatchFastPath()?.tryApplyFreshDirectAggregateFormulaMatrixBatch(refs, batch, source, potentialNewCells)) {
                return;
            }
            if (getFreshDirectScalarFormulaBatchFastPath()?.tryApplyFreshDirectScalarFormulaMatrixBatch(refs, batch, source, potentialNewCells)) {
                return;
            }
        }
        const triedFormulaLiteralBatchFastPath = hasLiteralMutations && source === 'local' && args.state.formulas.size !== 0;
        if (triedFormulaLiteralBatchFastPath && tryApplyCoalescedDirectScalarLiteralBatch(refs, batch, source, potentialNewCells)) {
            return;
        }
        if (source === 'local' &&
            hasLiteralMutations &&
            refs.length > 1 &&
            getExistingRectangularLiteralBatchFastPath().tryApplyExistingDenseRectangularNumericLiteralBatch(refs, batch, potentialNewCells)) {
            return;
        }
        if (hasLiteralMutations && source === 'local' && tryApplyFreshDenseRectangularNumericLiteralBatch(refs, batch, potentialNewCells)) {
            return;
        }
        if (!triedFormulaLiteralBatchFastPath && tryApplyCoalescedDirectScalarLiteralBatch(refs, batch, source, potentialNewCells)) {
            return;
        }
        if (mayContainFreshCells && hasFormulaMutations) {
            if (getFreshDirectAggregateFormulaBatchFastPath()?.tryApplyFreshDirectAggregateFormulaBatch(refs, batch, source, potentialNewCells)) {
                return;
            }
        }
        args.materializeDeferredStructuralFormulaSources();
        args.beginMutationCollection();
        let changedInputCount = 0;
        let formulaChangedCount = 0;
        let explicitChangedCount = 0;
        let topologyChanged = false;
        let compileMs = 0;
        const postRecalcDirectFormulaIndices = new DirectFormulaIndexCollection();
        const postRecalcDirectFormulaMetrics = {
            wasmFormulaCount: 0,
            jsFormulaCount: 0,
        };
        const lookupHandledInputCellIndices = [];
        const pendingExactLookupInvalidations = new Map();
        const pendingSortedLookupInvalidations = new Map();
        const queueHandledLookupInvalidation = (sheetId, sheetName, col, exact, sorted) => {
            const key = aggregateColumnDependencyKey(sheetId, col);
            if (exact) {
                pendingExactLookupInvalidations.set(key, { sheetName, col });
            }
            if (sorted) {
                pendingSortedLookupInvalidations.set(key, { sheetName, col });
            }
        };
        const hasGeneralEventListeners = args.state.events.hasListeners();
        const hasTrackedEventListeners = args.state.events.hasTrackedListeners();
        const hasWatchedCellListeners = args.state.events.hasCellListeners();
        const requiresChangedSet = hasGeneralEventListeners || hasTrackedEventListeners || hasWatchedCellListeners;
        const trackExplicitChanges = !isRestore && requiresChangedSet;
        const batchMayNeedFreshAggregateInputCoverage = refs.some((ref) => ref.mutation.kind === 'setCellValue' || ref.mutation.kind === 'clearCell');
        let hadCycleMembersBefore;
        const hadCycleMembersBeforeNow = () => (hadCycleMembersBefore ??= hasCycleMembersNow());
        const reservedNewCells = potentialNewCells ?? refs.length;
        args.state.workbook.cellStore.ensureCapacity(args.state.workbook.cellStore.size + reservedNewCells);
        args.ensureRecalcScratchCapacity(args.state.workbook.cellStore.size + reservedNewCells + 1);
        args.resetMaterializedCellScratch(reservedNewCells);
        const sheetNameResolver = createOperationSheetNameResolver(args.state.workbook);
        const trackedColumnDependencyFlags = createOperationTrackedColumnDependencyFlagResolver({
            hasTrackedExactLookupDependents,
            hasTrackedSortedLookupDependents,
            hasTrackedDirectRangeDependents,
        });
        const exactLookupImpactCaches = new Map();
        const clearTrackedColumnDependencyFlagCache = () => {
            trackedColumnDependencyFlags.clear();
            exactLookupImpactCaches.clear();
        };
        args.setBatchMutationDepth(args.getBatchMutationDepth() + 1);
        try {
            args.state.workbook.withBatchedColumnVersionUpdates(() => {
                refs.forEach((ref, refIndex) => {
                    args.checkEvaluationBudget();
                    const { sheetId, mutation } = ref;
                    const order = args.state.trackReplicaVersions && batch ? batchOpOrder(batch, refIndex) : undefined;
                    const existingIndex = resolveOperationExistingMutationCellIndex(args.state.workbook, ref);
                    switch (mutation.kind) {
                        case 'setCellValue': {
                            const sheetName = sheetNameResolver.resolve(sheetId);
                            const setValueResult = applySetCellValueMutation({
                                serviceArgs: args,
                                sheetId,
                                sheetName,
                                mutation,
                                existingIndex,
                                isRestore,
                                trackExplicitChanges,
                                order,
                                changedInputCount,
                                formulaChangedCount,
                                explicitChangedCount,
                                topologyChanged,
                                dependencyFlags: trackedColumnDependencyFlags.resolve(sheetId, mutation.col),
                                postRecalcDirectFormulaIndices,
                                exactLookupImpactCaches,
                                setCellEntityVersion,
                                isNullLiteralWriteNoOp,
                                canFastPathLiteralOverwrite,
                                readCellValueForLookup,
                                readApproximateNumericValueForLookup,
                                readExactNumericValueForLookup,
                                planExactLookupNumericColumnWrite,
                                planApproximateLookupNumericColumnWrite,
                                rebindValueSensitiveFormulaDependents: (cellIndex, counts) => {
                                    const reboundCount = counts.formulaChangedCount;
                                    const nextFormulaChangedCount = rebindDynamicFormulaDependents(cellIndex, counts.formulaChangedCount);
                                    return {
                                        ...counts,
                                        formulaChangedCount: nextFormulaChangedCount,
                                        topologyChanged: counts.topologyChanged || nextFormulaChangedCount !== reboundCount,
                                    };
                                },
                                markPostRecalcDirectFormulaDependents,
                                markDirectScalarDeltaClosure,
                                markPostRecalcDirectScalarNumericDependents,
                                markPostRecalcDirectLookupCurrentDependentsFromNumeric,
                                directScalarCellNumericValue,
                                noteExactLookupLiteralWriteWhenDirty,
                                noteSortedLookupLiteralWriteWhenDirty,
                                markAffectedDirectRangeDependents,
                                queueHandledLookupInvalidation,
                                noteHandledLookupInputCellIndex: (cellIndex) => lookupHandledInputCellIndices.push(cellIndex),
                                clearTrackedColumnDependencyFlagCache,
                                pruneCellIfOrphaned,
                            });
                            changedInputCount = setValueResult.changedInputCount;
                            formulaChangedCount = setValueResult.formulaChangedCount;
                            explicitChangedCount = setValueResult.explicitChangedCount;
                            topologyChanged = setValueResult.topologyChanged;
                            break;
                        }
                        case 'setCellFormula': {
                            const sheetName = sheetNameResolver.resolve(sheetId);
                            const formulaResult = applySetCellFormulaMutation({
                                serviceArgs: args,
                                sheetId,
                                sheetName,
                                mutation,
                                existingIndex,
                                isRestore,
                                trackExplicitChanges,
                                order,
                                changedInputCount,
                                formulaChangedCount,
                                explicitChangedCount,
                                topologyChanged,
                                compileMs,
                                dependencyFlags: trackedColumnDependencyFlags.resolve(sheetId, mutation.col),
                                batchMayNeedFreshAggregateInputCoverage,
                                postRecalcDirectFormulaIndices,
                                setCellEntityVersion,
                                readExactNumericValueForLookup,
                                clearTrackedColumnDependencyFlagCache,
                                applyDirectFormulaCurrentResult,
                                tryApplyFormulaReplacementAsDirectScalarDeltaRoot,
                                collectAffectedDirectRangeDependents,
                            });
                            changedInputCount = formulaResult.changedInputCount;
                            formulaChangedCount = formulaResult.formulaChangedCount;
                            explicitChangedCount = formulaResult.explicitChangedCount;
                            topologyChanged = formulaResult.topologyChanged;
                            compileMs = formulaResult.compileMs;
                            break;
                        }
                        case 'clearCell': {
                            const clearResult = applyClearCellMutation({
                                serviceArgs: args,
                                sheetId,
                                mutation,
                                existingIndex,
                                source,
                                isRestore,
                                trackExplicitChanges,
                                order,
                                changedInputCount,
                                formulaChangedCount,
                                explicitChangedCount,
                                topologyChanged,
                                dependencyFlags: trackedColumnDependencyFlags.resolve(sheetId, mutation.col),
                                postRecalcDirectFormulaIndices,
                                exactLookupImpactCaches,
                                resolveSheetName: (id) => sheetNameResolver.resolve(id),
                                setCellEntityVersion,
                                isClearCellNoOp,
                                canFastPathLiteralOverwrite,
                                readCellValueForLookup,
                                rebindValueSensitiveFormulaDependents: (cellIndex, counts) => {
                                    const reboundCount = counts.formulaChangedCount;
                                    const nextFormulaChangedCount = rebindDynamicFormulaDependents(cellIndex, counts.formulaChangedCount);
                                    return {
                                        ...counts,
                                        formulaChangedCount: nextFormulaChangedCount,
                                        topologyChanged: counts.topologyChanged || nextFormulaChangedCount !== reboundCount,
                                    };
                                },
                                markPostRecalcDirectFormulaDependents,
                                markDirectScalarDeltaClosure,
                                noteExactLookupLiteralWriteWhenDirty,
                                noteSortedLookupLiteralWriteWhenDirty,
                                markAffectedDirectRangeDependents,
                                clearTrackedColumnDependencyFlagCache,
                                pruneCellIfOrphaned,
                                normalizeHistoryDependencyPlaceholder,
                            });
                            changedInputCount = clearResult.changedInputCount;
                            formulaChangedCount = clearResult.formulaChangedCount;
                            explicitChangedCount = clearResult.explicitChangedCount;
                            topologyChanged = clearResult.topologyChanged;
                            break;
                        }
                        default:
                            assertNever(mutation);
                    }
                });
            });
            const reboundCount = formulaChangedCount;
            formulaChangedCount = args.syncDynamicRanges(formulaChangedCount);
            topologyChanged = topologyChanged || formulaChangedCount !== reboundCount;
        }
        finally {
            args.setBatchMutationDepth(args.getBatchMutationDepth() - 1);
        }
        pendingExactLookupInvalidations.forEach((entry) => args.invalidateExactLookupColumn(entry));
        pendingSortedLookupInvalidations.forEach((entry) => args.invalidateSortedLookupColumn(entry));
        if (batch) {
            markBatchApplied(args.state.replicaState, batch);
        }
        if (refs.length === 0) {
            if (!isRestore && batch) {
                emitBatch(batch);
            }
            return;
        }
        finalizeOperationRecalcAndEvents({
            serviceArgs: args,
            isRestore,
            topologyChanged,
            sheetDeleted: false,
            structuralInvalidation: false,
            refreshAllPivots: false,
            explicitChangedCount,
            changedInputCount,
            formulaChangedCount,
            compileMs,
            precomputedKernelSyncCellIndices: [],
            postRecalcDirectFormulaIndices,
            postRecalcDirectFormulaMetrics,
            lookupHandledInputCellIndices,
            invalidatedRanges: [],
            invalidatedRows: [],
            invalidatedColumns: [],
            invalidatedRowCount: 0,
            invalidatedColumnCount: 0,
            hadCycleMembersBeforeNow,
            markCycleMemberInputsChanged,
            canSkipDirtyTraversalForChangedInputs,
            directFormulaCallbacks: {
                applyDirectFormulaCurrentResult,
                applyDirectFormulaNumericDelta,
                applyDirectScalarCurrentValue,
                tryApplyDirectScalarDeltas,
                tryApplyDirectFormulaDeltas,
                countPostRecalcDirectFormulaMetric,
            },
            shouldMaterializeChangedCells: (listenerState) => listenerState.hasGeneralEventListeners,
        });
        if (isRestore && !hasTrackedEventListeners) {
            return;
        }
        if (batch) {
            void args.state.getSyncClientConnection()?.send(batch);
            emitBatch(batch);
        }
    };
}
//# sourceMappingURL=operation-cell-mutation-applier.js.map