import { ErrorCode, ValueTag } from '@bilig/protocol';
import { makeCellEntity } from '../../entity-ids.js';
import { directScalarDeltaFromNumbers, directScalarDeltaFromValues, directScalarValueNumber } from './direct-scalar-helpers.js';
export function initialDirectScalarLinearDeltaClosureCapacity(scalarDeltaClosureLimit) {
    if (!Number.isFinite(scalarDeltaClosureLimit) || scalarDeltaClosureLimit <= 0) {
        return 1;
    }
    return Math.max(1, Math.min(Math.trunc(scalarDeltaClosureLimit) + 1, 2_048));
}
function affineDirectScalarDeltaFromInputDelta(directScalar, inputCellIndex, inputDelta) {
    if (directScalar.kind === 'abs') {
        return undefined;
    }
    const left = directScalar.left;
    const right = directScalar.right;
    if (left.kind === 'cell' && left.cellIndex === inputCellIndex && right.kind === 'literal-number') {
        switch (directScalar.operator) {
            case '+':
            case '-':
                return inputDelta;
            case '*':
                return inputDelta * right.value;
            case '/':
                return right.value === 0 ? undefined : inputDelta / right.value;
        }
    }
    if (right.kind === 'cell' && right.cellIndex === inputCellIndex && left.kind === 'literal-number') {
        switch (directScalar.operator) {
            case '+':
                return inputDelta;
            case '-':
                return -inputDelta;
            case '*':
                return left.value * inputDelta;
            case '/':
                return undefined;
        }
    }
    return undefined;
}
function directScalarPreservesInputDelta(directScalar, inputCellIndex) {
    const left = directScalar.left;
    const right = directScalar.right;
    if (left.kind === 'cell' && left.cellIndex === inputCellIndex && right.kind === 'literal-number') {
        return directScalar.operator === '+' || directScalar.operator === '-';
    }
    return directScalar.operator === '+' && right.kind === 'cell' && right.cellIndex === inputCellIndex && left.kind === 'literal-number';
}
export function createOperationDirectPostRecalcMarkers(args) {
    const reverseCellEdges = args.reverseCellEdges;
    const edgeArena = args.edgeArena;
    const getSingleCellDependent = reverseCellEdges !== undefined && edgeArena !== undefined
        ? (cellIndex) => {
            const slice = reverseCellEdges[cellIndex] ?? edgeArena.empty();
            if (slice.len === 0 || slice.ptr < 0) {
                return -1;
            }
            return slice.len === 1 ? edgeArena.singletonValue(slice) : -2;
        }
        : (args.getSingleCellDependent ?? ((cellIndex) => args.getSingleEntityDependent(makeCellEntity(cellIndex))));
    const getCellDependents = args.getCellDependents ?? ((cellIndex) => args.getEntityDependents(makeCellEntity(cellIndex)));
    const cleanScalarClosureScratch = {};
    const tryDirectScalarNumericDelta = (directScalar, changedCellIndex, oldValue, newValue) => {
        return directScalarDeltaFromValues(directScalar, changedCellIndex, oldValue, newValue, args.readDirectScalarCellNumber);
    };
    const tryDirectScalarNumericDeltaFromNumbers = (directScalar, changedCellIndex, oldChangedNumber, newChangedNumber) => {
        return directScalarDeltaFromNumbers(directScalar, changedCellIndex, oldChangedNumber, newChangedNumber, args.readDirectScalarCellNumber);
    };
    const addDirectLookupCurrentResultIfChanged = (formulaCellIndex, result, postRecalcDirectFormulaIndices) => {
        if (!args.directScalarCurrentResultMatchesCell(formulaCellIndex, result)) {
            postRecalcDirectFormulaIndices.addCurrentResult(formulaCellIndex, result);
            return true;
        }
        return false;
    };
    const canUseDirectFormulaPostRecalc = (formulaCellIndex) => {
        const formula = args.state.formulas.get(formulaCellIndex);
        return (formula !== undefined &&
            (formula.directLookup !== undefined ||
                formula.directAggregate !== undefined ||
                formula.directScalar !== undefined ||
                formula.directCriteria !== undefined) &&
            args.hasNoCellDependents(formulaCellIndex));
    };
    const markPostRecalcDirectLookupCurrentDependentsFromNumeric = (cellIndex, exactLookupValue, approximateLookupValue, postRecalcDirectFormulaIndices) => {
        const singleDependent = getSingleCellDependent(cellIndex);
        if (singleDependent === -1) {
            return true;
        }
        if (singleDependent >= 0) {
            if (!canUseDirectFormulaPostRecalc(singleDependent)) {
                return false;
            }
            const directLookupResult = args.lookupCurrent.tryDirectUniformLookupCurrentResultFromNumeric(singleDependent, exactLookupValue, approximateLookupValue);
            if (directLookupResult === undefined) {
                return false;
            }
            addDirectLookupCurrentResultIfChanged(singleDependent, directLookupResult, postRecalcDirectFormulaIndices);
            return true;
        }
        const dependents = getCellDependents(cellIndex);
        for (let index = 0; index < dependents.length; index += 1) {
            const formulaCellIndex = dependents[index];
            if (!canUseDirectFormulaPostRecalc(formulaCellIndex)) {
                return false;
            }
            if (!args.lookupCurrent.canEvaluateDirectUniformLookupCurrentResultFromNumeric(formulaCellIndex, exactLookupValue, approximateLookupValue)) {
                return false;
            }
        }
        for (let index = 0; index < dependents.length; index += 1) {
            const formulaCellIndex = dependents[index];
            const directLookupResult = args.lookupCurrent.tryDirectUniformLookupCurrentResultFromNumeric(formulaCellIndex, exactLookupValue, approximateLookupValue);
            if (directLookupResult === undefined) {
                return false;
            }
            addDirectLookupCurrentResultIfChanged(formulaCellIndex, directLookupResult, postRecalcDirectFormulaIndices);
        }
        return true;
    };
    const markPostRecalcDirectFormulaDependents = (cellIndex, postRecalcDirectFormulaIndices, oldValue, newValue) => {
        const singleDependent = getSingleCellDependent(cellIndex);
        if (singleDependent === -1) {
            return true;
        }
        if (singleDependent >= 0) {
            if (!canUseDirectFormulaPostRecalc(singleDependent)) {
                return false;
            }
            if (oldValue === undefined || newValue === undefined) {
                postRecalcDirectFormulaIndices.add(singleDependent);
                return true;
            }
            const directScalar = args.state.formulas.get(singleDependent)?.directScalar;
            if (directScalar !== undefined) {
                const delta = tryDirectScalarNumericDelta(directScalar, cellIndex, oldValue, newValue);
                if (delta !== undefined) {
                    postRecalcDirectFormulaIndices.addScalarDelta(singleDependent, delta);
                    return true;
                }
            }
            const directLookupResult = args.lookupCurrent.tryDirectUniformLookupCurrentResult(singleDependent);
            if (directLookupResult !== undefined) {
                if (!addDirectLookupCurrentResultIfChanged(singleDependent, directLookupResult, postRecalcDirectFormulaIndices)) {
                    postRecalcDirectFormulaIndices.markDirectFormulaInputCovered(cellIndex);
                }
                return true;
            }
            const directLookup = args.state.formulas.get(singleDependent)?.directLookup;
            if (directLookup?.kind === 'exact' && directLookup.operandCellIndex === cellIndex) {
                const exactResult = args.lookupCurrent.tryDirectExactLookupCurrentResult(directLookup, newValue);
                if (exactResult !== undefined) {
                    if (!addDirectLookupCurrentResultIfChanged(singleDependent, exactResult, postRecalcDirectFormulaIndices)) {
                        postRecalcDirectFormulaIndices.markDirectFormulaInputCovered(cellIndex);
                    }
                    return true;
                }
            }
            postRecalcDirectFormulaIndices.add(singleDependent);
            return true;
        }
        const dependents = getCellDependents(cellIndex);
        for (let index = 0; index < dependents.length; index += 1) {
            if (!canUseDirectFormulaPostRecalc(dependents[index])) {
                return false;
            }
        }
        if (oldValue !== undefined && newValue !== undefined && dependents.length > 16) {
            let canUseBulkScalarDeltas = true;
            let commonDelta;
            let allDeltasMatch = true;
            for (let index = 0; index < dependents.length; index += 1) {
                const formulaCellIndex = dependents[index];
                const directScalar = args.state.formulas.get(formulaCellIndex)?.directScalar;
                const delta = directScalar === undefined ? undefined : tryDirectScalarNumericDelta(directScalar, cellIndex, oldValue, newValue);
                if (delta === undefined) {
                    canUseBulkScalarDeltas = false;
                    break;
                }
                if (commonDelta === undefined) {
                    commonDelta = delta;
                }
                else if (!Object.is(commonDelta, delta)) {
                    allDeltasMatch = false;
                }
            }
            if (canUseBulkScalarDeltas) {
                if (allDeltasMatch && commonDelta !== undefined) {
                    postRecalcDirectFormulaIndices.appendConstantDelta(dependents, commonDelta, 'scalar');
                }
                else {
                    const deltaCellIndices = [];
                    const deltas = [];
                    for (let index = 0; index < dependents.length; index += 1) {
                        const formulaCellIndex = dependents[index];
                        const directScalar = args.state.formulas.get(formulaCellIndex)?.directScalar;
                        const delta = directScalar === undefined ? undefined : tryDirectScalarNumericDelta(directScalar, cellIndex, oldValue, newValue);
                        if (delta === undefined) {
                            canUseBulkScalarDeltas = false;
                            break;
                        }
                        deltaCellIndices.push(formulaCellIndex);
                        deltas.push(delta);
                    }
                    if (!canUseBulkScalarDeltas) {
                        return false;
                    }
                    postRecalcDirectFormulaIndices.appendDeltas(deltaCellIndices, deltas, 'scalar');
                }
                return true;
            }
        }
        for (let index = 0; index < dependents.length; index += 1) {
            const formulaCellIndex = dependents[index];
            const directScalar = args.state.formulas.get(formulaCellIndex)?.directScalar;
            if (oldValue === undefined || newValue === undefined) {
                postRecalcDirectFormulaIndices.add(formulaCellIndex);
                continue;
            }
            if (directScalar !== undefined) {
                const delta = tryDirectScalarNumericDelta(directScalar, cellIndex, oldValue, newValue);
                if (delta !== undefined) {
                    postRecalcDirectFormulaIndices.addScalarDelta(formulaCellIndex, delta);
                    continue;
                }
            }
            const directLookupResult = args.lookupCurrent.tryDirectUniformLookupCurrentResult(formulaCellIndex);
            if (directLookupResult !== undefined) {
                addDirectLookupCurrentResultIfChanged(formulaCellIndex, directLookupResult, postRecalcDirectFormulaIndices);
                continue;
            }
            const directLookup = args.state.formulas.get(formulaCellIndex)?.directLookup;
            if (directLookup?.kind === 'exact' && directLookup.operandCellIndex === cellIndex) {
                const exactResult = args.lookupCurrent.tryDirectExactLookupCurrentResult(directLookup, newValue);
                if (exactResult !== undefined) {
                    addDirectLookupCurrentResultIfChanged(formulaCellIndex, exactResult, postRecalcDirectFormulaIndices);
                    continue;
                }
            }
            postRecalcDirectFormulaIndices.add(formulaCellIndex);
        }
        return true;
    };
    const markPostRecalcDirectScalarNumericDependents = (cellIndex, oldNumber, newNumber, postRecalcDirectFormulaIndices, exactLookupValue, approximateLookupValue) => {
        const singleDependent = getSingleCellDependent(cellIndex);
        if (singleDependent === -1) {
            return true;
        }
        if (singleDependent >= 0) {
            if (!canUseDirectFormulaPostRecalc(singleDependent)) {
                return false;
            }
            const directScalar = args.state.formulas.get(singleDependent)?.directScalar;
            const delta = directScalar === undefined ? undefined : tryDirectScalarNumericDeltaFromNumbers(directScalar, cellIndex, oldNumber, newNumber);
            if (delta === undefined) {
                const directLookupResult = args.lookupCurrent.tryDirectUniformLookupCurrentResultFromNumeric(singleDependent, exactLookupValue, approximateLookupValue);
                if (directLookupResult === undefined) {
                    return false;
                }
                if (!addDirectLookupCurrentResultIfChanged(singleDependent, directLookupResult, postRecalcDirectFormulaIndices)) {
                    postRecalcDirectFormulaIndices.markDirectFormulaInputCovered(cellIndex);
                }
                return true;
            }
            postRecalcDirectFormulaIndices.addScalarDelta(singleDependent, delta);
            return true;
        }
        const dependents = getCellDependents(cellIndex);
        if (dependents.length === 0) {
            return true;
        }
        let commonDelta;
        let allDeltasMatch = true;
        for (let index = 0; index < dependents.length; index += 1) {
            const formulaCellIndex = dependents[index];
            if (!canUseDirectFormulaPostRecalc(formulaCellIndex)) {
                return false;
            }
            const directScalar = args.state.formulas.get(formulaCellIndex)?.directScalar;
            const delta = directScalar === undefined ? undefined : tryDirectScalarNumericDeltaFromNumbers(directScalar, cellIndex, oldNumber, newNumber);
            if (delta === undefined) {
                return false;
            }
            if (commonDelta === undefined) {
                commonDelta = delta;
            }
            else if (!Object.is(commonDelta, delta)) {
                allDeltasMatch = false;
                break;
            }
        }
        if (allDeltasMatch && commonDelta !== undefined) {
            postRecalcDirectFormulaIndices.appendConstantDelta(dependents, commonDelta, 'scalar');
            return true;
        }
        const deltas = [];
        for (let index = 0; index < dependents.length; index += 1) {
            const formulaCellIndex = dependents[index];
            if (!canUseDirectFormulaPostRecalc(formulaCellIndex)) {
                return false;
            }
            const directScalar = args.state.formulas.get(formulaCellIndex)?.directScalar;
            const delta = directScalar === undefined ? undefined : tryDirectScalarNumericDeltaFromNumbers(directScalar, cellIndex, oldNumber, newNumber);
            if (delta === undefined) {
                return false;
            }
            deltas[index] = delta;
        }
        postRecalcDirectFormulaIndices.appendDeltas(dependents, deltas, 'scalar');
        return true;
    };
    const tryMarkDirectScalarLinearDeltaClosure = (rootCellIndex, oldValue, newValue, postRecalcDirectFormulaIndices) => {
        const oldRootNumber = directScalarValueNumber(oldValue);
        const newRootNumber = directScalarValueNumber(newValue);
        if (oldRootNumber === undefined || newRootNumber === undefined) {
            return false;
        }
        let currentCellIndex = rootCellIndex;
        let oldNumber = oldRootNumber;
        let newNumber = newRootNumber;
        let inputDelta = newRootNumber - oldRootNumber;
        const canSkipAllDirectFormulaColumnVersions = args.canSkipAllDirectFormulaColumnVersions?.() === true;
        const cellStore = args.state.workbook.cellStore;
        const formulas = args.state.formulas;
        const flags = cellStore.flags;
        const tags = cellStore.tags;
        const stringIds = cellStore.stringIds;
        const errors = cellStore.errors;
        const formulaOutputFlags = 64 /* CellFlags.SpillChild */ | 128 /* CellFlags.PivotOutput */;
        const directScalarCellNumericValue = args.directScalarCellNumericValue;
        const canSkipDirectFormulaColumnVersion = args.canSkipDirectFormulaColumnVersion;
        if (tryMarkCleanSameDeltaDirectScalarClosure({
            rootCellIndex,
            inputDelta,
            getSingleCellDependent,
            formulas,
            ...(args.state.directScalarDeltaInputCellIndices === undefined
                ? {}
                : { directScalarDeltaInputCellIndices: args.state.directScalarDeltaInputCellIndices }),
            flags,
            tags,
            stringIds,
            errors,
            formulaOutputFlags,
            canSkipAllDirectFormulaColumnVersions,
            canSkipDirectFormulaColumnVersion,
            postRecalcDirectFormulaIndices,
            scalarDeltaClosureLimit: args.scalarDeltaClosureLimit,
            scratch: cleanScalarClosureScratch,
        })) {
            return true;
        }
        let closureCount = 0;
        let cellIndices = new Uint32Array(initialDirectScalarLinearDeltaClosureCapacity(args.scalarDeltaClosureLimit));
        let deltas;
        let commonDelta;
        let canUseValidatedTerminalWrites = true;
        let canUseCleanTerminalWrites = true;
        let sortedClosureCellIndices = true;
        let previousClosureCellIndex = -1;
        for (;;) {
            if (closureCount > args.scalarDeltaClosureLimit) {
                return false;
            }
            const formulaCellIndex = getSingleCellDependent(currentCellIndex);
            if (formulaCellIndex === -1) {
                break;
            }
            if (formulaCellIndex < 0) {
                return false;
            }
            if (formulaCellIndex === rootCellIndex) {
                return false;
            }
            const formula = formulas.get(formulaCellIndex);
            if (!formula ||
                formula.directScalar === undefined ||
                formula.compiled.volatile ||
                formula.compiled.producesSpill ||
                ((flags[formulaCellIndex] ?? 0) & 8 /* CellFlags.InCycle */) !== 0) {
                return false;
            }
            let formulaOldNumber;
            let formulaNewNumber;
            let formulaDelta;
            const directScalar = formula.directScalar;
            const affineFormulaDelta = affineDirectScalarDeltaFromInputDelta(directScalar, currentCellIndex, inputDelta);
            if (affineFormulaDelta !== undefined) {
                if (tags[formulaCellIndex] !== ValueTag.Number) {
                    return false;
                }
                formulaDelta = affineFormulaDelta;
            }
            else {
                if (oldNumber === undefined) {
                    oldNumber = directScalarCellNumericValue(currentCellIndex);
                    if (oldNumber === undefined) {
                        return false;
                    }
                    newNumber = oldNumber + inputDelta;
                }
                if (newNumber === undefined) {
                    return false;
                }
            }
            if (formulaDelta === undefined && directScalar.kind !== 'abs') {
                if (oldNumber === undefined || newNumber === undefined) {
                    return false;
                }
                const currentOldNumber = oldNumber;
                const currentNewNumber = newNumber;
                const resultOffset = directScalar.resultOffset ?? 0;
                const left = directScalar.left;
                const right = directScalar.right;
                const leftIsInput = left.kind === 'cell' && left.cellIndex === currentCellIndex;
                const rightIsInput = right.kind === 'cell' && right.cellIndex === currentCellIndex;
                if (leftIsInput && right.kind === 'literal-number') {
                    switch (directScalar.operator) {
                        case '+':
                            formulaOldNumber = currentOldNumber + right.value + resultOffset;
                            formulaNewNumber = currentNewNumber + right.value + resultOffset;
                            break;
                        case '-':
                            formulaOldNumber = currentOldNumber - right.value + resultOffset;
                            formulaNewNumber = currentNewNumber - right.value + resultOffset;
                            break;
                        case '*':
                            formulaOldNumber = currentOldNumber * right.value + resultOffset;
                            formulaNewNumber = currentNewNumber * right.value + resultOffset;
                            break;
                        case '/':
                            if (right.value !== 0) {
                                formulaOldNumber = currentOldNumber / right.value + resultOffset;
                                formulaNewNumber = currentNewNumber / right.value + resultOffset;
                            }
                            break;
                    }
                }
                else if (rightIsInput && left.kind === 'literal-number') {
                    switch (directScalar.operator) {
                        case '+':
                            formulaOldNumber = left.value + currentOldNumber + resultOffset;
                            formulaNewNumber = left.value + currentNewNumber + resultOffset;
                            break;
                        case '-':
                            formulaOldNumber = left.value - currentOldNumber + resultOffset;
                            formulaNewNumber = left.value - currentNewNumber + resultOffset;
                            break;
                        case '*':
                            formulaOldNumber = left.value * currentOldNumber + resultOffset;
                            formulaNewNumber = left.value * currentNewNumber + resultOffset;
                            break;
                        case '/':
                            break;
                    }
                }
            }
            if (formulaDelta === undefined) {
                if (formulaOldNumber === undefined || formulaNewNumber === undefined) {
                    if (oldNumber === undefined || newNumber === undefined) {
                        return false;
                    }
                    formulaOldNumber = directScalarCellNumericValue(formulaCellIndex);
                    formulaDelta = tryDirectScalarNumericDeltaFromNumbers(directScalar, currentCellIndex, oldNumber, newNumber);
                }
                else {
                    if (tags[formulaCellIndex] !== ValueTag.Number) {
                        return false;
                    }
                    formulaDelta = formulaNewNumber - formulaOldNumber;
                }
            }
            if (formulaDelta === undefined) {
                return false;
            }
            if (formulaOldNumber === undefined && formulaNewNumber !== undefined) {
                return false;
            }
            if (canUseValidatedTerminalWrites && !canSkipAllDirectFormulaColumnVersions && !canSkipDirectFormulaColumnVersion(formulaCellIndex)) {
                canUseValidatedTerminalWrites = false;
            }
            if (canUseCleanTerminalWrites &&
                (tags[formulaCellIndex] !== ValueTag.Number ||
                    (stringIds?.[formulaCellIndex] ?? 0) !== 0 ||
                    (errors?.[formulaCellIndex] ?? ErrorCode.None) !== ErrorCode.None ||
                    ((flags[formulaCellIndex] ?? 0) & formulaOutputFlags) !== 0)) {
                canUseCleanTerminalWrites = false;
            }
            if (commonDelta === undefined) {
                commonDelta = formulaDelta;
            }
            else if (!Object.is(commonDelta, formulaDelta) && deltas === undefined) {
                deltas = [];
                for (let index = 0; index < closureCount; index += 1) {
                    deltas[index] = commonDelta;
                }
            }
            if (closureCount >= cellIndices.length) {
                const nextCellIndices = new Uint32Array(cellIndices.length * 2);
                nextCellIndices.set(cellIndices);
                cellIndices = nextCellIndices;
            }
            if (formulaCellIndex <= previousClosureCellIndex) {
                sortedClosureCellIndices = false;
            }
            previousClosureCellIndex = formulaCellIndex;
            cellIndices[closureCount] = formulaCellIndex;
            if (deltas) {
                deltas.push(formulaDelta);
            }
            currentCellIndex = formulaCellIndex;
            if (formulaOldNumber === undefined) {
                oldNumber = undefined;
                newNumber = undefined;
            }
            else {
                oldNumber = formulaOldNumber;
                newNumber = formulaNewNumber ?? formulaOldNumber + formulaDelta;
            }
            inputDelta = formulaDelta;
            closureCount += 1;
        }
        if (closureCount === 0) {
            return false;
        }
        const closureCellIndices = cellIndices.subarray(0, closureCount);
        if (deltas) {
            if (sortedClosureCellIndices) {
                postRecalcDirectFormulaIndices.appendSortedDeltas(closureCellIndices, deltas, 'scalar');
            }
            else {
                postRecalcDirectFormulaIndices.appendDeltas(closureCellIndices, deltas, 'scalar');
            }
        }
        else if (commonDelta !== undefined) {
            if (sortedClosureCellIndices) {
                postRecalcDirectFormulaIndices.appendSortedConstantDelta(closureCellIndices, commonDelta, 'scalar');
            }
            else {
                postRecalcDirectFormulaIndices.appendConstantDelta(closureCellIndices, commonDelta, 'scalar');
            }
        }
        if (canUseValidatedTerminalWrites) {
            postRecalcDirectFormulaIndices.markScalarDeltaCellsValidated();
            postRecalcDirectFormulaIndices.markScalarDeltaCellsTrustedDirectScalarFormulas();
            if (canUseCleanTerminalWrites) {
                postRecalcDirectFormulaIndices.markScalarDeltaCellsCleanNumber();
            }
        }
        return true;
    };
    const markDirectScalarDeltaClosure = (rootCellIndex, oldValue, newValue, postRecalcDirectFormulaIndices) => {
        const rootDependent = getSingleCellDependent(rootCellIndex);
        if (rootDependent < 0 || postRecalcDirectFormulaIndices.hasDelta(rootDependent)) {
            return;
        }
        if (getSingleCellDependent(rootDependent) === -1) {
            return;
        }
        if (tryMarkDirectScalarLinearDeltaClosure(rootCellIndex, oldValue, newValue, postRecalcDirectFormulaIndices)) {
            return;
        }
        const pending = [
            { cellIndex: rootCellIndex, oldValue, newValue },
        ];
        const closureDeltas = new Map();
        const visited = new Set([rootCellIndex]);
        for (let cursor = 0; cursor < pending.length; cursor += 1) {
            if (closureDeltas.size > args.scalarDeltaClosureLimit) {
                return;
            }
            const current = pending[cursor];
            const dependents = getCellDependents(current.cellIndex);
            for (let index = 0; index < dependents.length; index += 1) {
                const formulaCellIndex = dependents[index];
                if (visited.has(formulaCellIndex)) {
                    return;
                }
                const formula = args.state.formulas.get(formulaCellIndex);
                if (!formula ||
                    formula.directScalar === undefined ||
                    formula.compiled.volatile ||
                    formula.compiled.producesSpill ||
                    ((args.state.workbook.cellStore.flags[formulaCellIndex] ?? 0) & 8 /* CellFlags.InCycle */) !== 0) {
                    return;
                }
                const formulaDelta = tryDirectScalarNumericDelta(formula.directScalar, current.cellIndex, current.oldValue, current.newValue);
                if (formulaDelta === undefined) {
                    return;
                }
                const formulaOldValue = args.state.workbook.cellStore.getValue(formulaCellIndex, (id) => args.state.strings.get(id));
                if (formulaOldValue.tag !== ValueTag.Number) {
                    return;
                }
                const accumulatedDelta = (closureDeltas.get(formulaCellIndex) ?? 0) + formulaDelta;
                closureDeltas.set(formulaCellIndex, accumulatedDelta);
                visited.add(formulaCellIndex);
                pending.push({
                    cellIndex: formulaCellIndex,
                    oldValue: formulaOldValue,
                    newValue: { tag: ValueTag.Number, value: formulaOldValue.value + accumulatedDelta },
                });
            }
        }
        closureDeltas.forEach((delta, formulaCellIndex) => {
            postRecalcDirectFormulaIndices.addScalarDelta(formulaCellIndex, delta);
        });
    };
    return {
        canUseDirectFormulaPostRecalc,
        markDirectScalarDeltaClosure,
        markPostRecalcDirectFormulaDependents,
        markPostRecalcDirectLookupCurrentDependentsFromNumeric,
        markPostRecalcDirectScalarNumericDependents,
        tryDirectScalarNumericDelta,
        tryDirectScalarNumericDeltaFromNumbers,
        tryMarkDirectScalarLinearDeltaClosure,
    };
}
function tryMarkCleanSameDeltaDirectScalarClosure(args) {
    let currentCellIndex = args.rootCellIndex;
    let closureCount = 0;
    let sortedClosureCellIndices = true;
    let previousClosureCellIndex = -1;
    const scratch = args.scratch;
    const usingScratch = args.postRecalcDirectFormulaIndices.size === 0 && scratch !== undefined;
    let cellIndices = usingScratch
        ? (scratch.cellIndices ??= new Uint32Array(initialDirectScalarLinearDeltaClosureCapacity(args.scalarDeltaClosureLimit)))
        : new Uint32Array(initialDirectScalarLinearDeltaClosureCapacity(args.scalarDeltaClosureLimit));
    for (;;) {
        if (closureCount > args.scalarDeltaClosureLimit) {
            return false;
        }
        const formulaCellIndex = args.getSingleCellDependent(currentCellIndex);
        if (formulaCellIndex === -1) {
            break;
        }
        if (formulaCellIndex < 0 || formulaCellIndex === args.rootCellIndex) {
            return false;
        }
        let preservesInputDelta = args.directScalarDeltaInputCellIndices?.[formulaCellIndex] === currentCellIndex;
        let formula;
        let directScalar;
        if (!preservesInputDelta) {
            formula = args.formulas.get(formulaCellIndex);
            directScalar = formula?.directScalar;
            preservesInputDelta =
                formula !== undefined &&
                    !formula.compiled.volatile &&
                    !formula.compiled.producesSpill &&
                    directScalar?.kind === 'binary' &&
                    (directScalar.deltaInputCellIndex === currentCellIndex ||
                        (directScalar.deltaInputCellIndex === undefined && directScalarPreservesInputDelta(directScalar, currentCellIndex)));
        }
        if (!preservesInputDelta ||
            ((args.flags[formulaCellIndex] ?? 0) & 8 /* CellFlags.InCycle */) !== 0 ||
            args.tags[formulaCellIndex] !== ValueTag.Number ||
            (args.stringIds[formulaCellIndex] ?? 0) !== 0 ||
            (args.errors[formulaCellIndex] ?? ErrorCode.None) !== ErrorCode.None ||
            ((args.flags[formulaCellIndex] ?? 0) & args.formulaOutputFlags) !== 0 ||
            (!args.canSkipAllDirectFormulaColumnVersions && !args.canSkipDirectFormulaColumnVersion(formulaCellIndex))) {
            return false;
        }
        if (closureCount >= cellIndices.length) {
            const nextCellIndices = new Uint32Array(cellIndices.length * 2);
            nextCellIndices.set(cellIndices);
            cellIndices = nextCellIndices;
            if (usingScratch) {
                scratch.cellIndices = nextCellIndices;
            }
        }
        if (formulaCellIndex <= previousClosureCellIndex) {
            sortedClosureCellIndices = false;
        }
        previousClosureCellIndex = formulaCellIndex;
        cellIndices[closureCount] = formulaCellIndex;
        closureCount += 1;
        currentCellIndex = formulaCellIndex;
    }
    if (closureCount === 0) {
        return false;
    }
    const closureCellIndices = cellIndices.subarray(0, closureCount);
    if (sortedClosureCellIndices) {
        args.postRecalcDirectFormulaIndices.appendSortedConstantDelta(closureCellIndices, args.inputDelta, 'scalar');
    }
    else {
        args.postRecalcDirectFormulaIndices.appendConstantDelta(closureCellIndices, args.inputDelta, 'scalar');
    }
    args.postRecalcDirectFormulaIndices.markScalarDeltaCellsValidated();
    args.postRecalcDirectFormulaIndices.markScalarDeltaCellsTrustedDirectScalarFormulas();
    args.postRecalcDirectFormulaIndices.markScalarDeltaCellsCleanNumber();
    args.postRecalcDirectFormulaIndices.markScalarDeltaCellsCycleIndependent();
    return true;
}
//# sourceMappingURL=operation-direct-post-recalc-markers.js.map