import { ErrorCode, FormulaMode, ValueTag } from '@bilig/protocol';
import { addEngineCounter } from '../../perf/engine-counters.js';
import { mergeChangedCellIndices } from './operation-change-helpers.js';
const EMPTY_CHANGED_CELLS = new Uint32Array(0);
export function applyPostRecalcDirectFormulaChanges(args) {
    if (args.collection.size === 0) {
        return args.recalculated;
    }
    const captureChanged = args.captureChanged ?? true;
    const hasCycleSensitiveFormula = args.collection.hasCycleIndependentScalarDeltaCells() ? false : hasAnyCycleSensitiveFormulaCell(args);
    const constantScalarChanged = !args.didRunRecalc && !hasCycleSensitiveFormula ? args.tryApplyDirectScalarDeltas(args.collection, captureChanged) : undefined;
    if (constantScalarChanged !== undefined) {
        return mergeChangedCellIndices(args.recalculated, constantScalarChanged);
    }
    const directDeltaChanged = !args.didRunRecalc && !hasCycleSensitiveFormula ? args.tryApplyDirectFormulaDeltas(args.collection, captureChanged) : undefined;
    if (directDeltaChanged !== undefined) {
        return mergeChangedCellIndices(args.recalculated, directDeltaChanged);
    }
    const singleDirectChanged = tryApplySinglePostRecalcDirectFormula(args, captureChanged);
    if (singleDirectChanged !== undefined) {
        return mergeChangedCellIndices(args.recalculated, singleDirectChanged);
    }
    return applyDirectFormulaFallbacks(args, captureChanged);
}
export function countOperationPostRecalcDirectFormulaMetric(input) {
    const formula = input.formulas.get(input.cellIndex);
    if (!formula || (formula.directScalar === undefined && formula.directAggregate === undefined && formula.directCriteria === undefined)) {
        return;
    }
    if (formula.compiled.mode === FormulaMode.WasmFastPath) {
        input.counts.wasmFormulaCount += 1;
        return;
    }
    input.counts.jsFormulaCount += 1;
}
export function tryApplySinglePostRecalcDirectFormula(args, captureChanged = true) {
    if (args.didRunRecalc || args.collection.size !== 1) {
        return undefined;
    }
    const cellIndex = args.collection.getCellIndexAt(0);
    if (isCycleSensitiveFormulaCell(args, cellIndex)) {
        return undefined;
    }
    const currentResult = args.collection.getCurrentResultAt(0);
    if (currentResult !== undefined) {
        return args.applyDirectFormulaCurrentResult(cellIndex, currentResult)
            ? captureChanged
                ? Uint32Array.of(cellIndex)
                : EMPTY_CHANGED_CELLS
            : undefined;
    }
    const formula = args.state.formulas.get(cellIndex);
    if (!isActivePostRecalcDirectFormula(formula)) {
        return undefined;
    }
    const delta = args.collection.getDeltaAt(0);
    if (delta !== undefined) {
        if (!args.applyDirectFormulaNumericDelta(cellIndex, delta)) {
            return undefined;
        }
        countDirectDeltaApplication(args, args.state.formulas.get(cellIndex));
        return captureChanged ? Uint32Array.of(cellIndex) : EMPTY_CHANGED_CELLS;
    }
    if (formula?.directScalar !== undefined &&
        !formula.compiled.producesSpill &&
        args.applyDirectScalarCurrentValue(cellIndex, formula.directScalar)) {
        args.countPostRecalcDirectFormulaMetric(cellIndex, args.metrics);
        return captureChanged ? Uint32Array.of(cellIndex) : EMPTY_CHANGED_CELLS;
    }
    return undefined;
}
function applyDirectFormulaFallbacks(args, captureChanged) {
    const postRecalcChanged = captureChanged ? new Uint32Array(args.collection.size) : EMPTY_CHANGED_CELLS;
    let postRecalcChangedCount = 0;
    let postRecalcExtraChanged;
    let directAggregateDeltaApplicationCount = 0;
    let directScalarDeltaApplicationCount = 0;
    const directCriteriaCurrentResults = !args.didRunRecalc ? new Map() : undefined;
    args.state.workbook.withBatchedColumnVersionUpdates(() => {
        args.collection.forEachIndexed((cellIndex, directIndex) => {
            if (isCycleSensitiveFormulaCell(args, cellIndex)) {
                return;
            }
            const formula = args.state.formulas.get(cellIndex);
            if (formula === undefined) {
                return;
            }
            const currentResult = args.collection.getCurrentResultAt(directIndex);
            if (!args.didRunRecalc && currentResult !== undefined && args.applyDirectFormulaCurrentResult(cellIndex, currentResult)) {
                if (captureChanged) {
                    postRecalcChanged[postRecalcChangedCount++] = cellIndex;
                }
                return;
            }
            const delta = args.collection.getDeltaAt(directIndex);
            if (!args.didRunRecalc && delta !== undefined && args.applyDirectFormulaNumericDelta(cellIndex, delta)) {
                if (formula?.directAggregate !== undefined || formula?.directCriteria !== undefined) {
                    directAggregateDeltaApplicationCount += 1;
                }
                if (formula?.directScalar !== undefined) {
                    directScalarDeltaApplicationCount += 1;
                }
                if (captureChanged) {
                    postRecalcChanged[postRecalcChangedCount++] = cellIndex;
                }
                return;
            }
            if (!args.didRunRecalc &&
                formula?.directScalar !== undefined &&
                !formula.compiled.producesSpill &&
                args.applyDirectScalarCurrentValue(cellIndex, formula.directScalar)) {
                args.countPostRecalcDirectFormulaMetric(cellIndex, args.metrics);
                if (captureChanged) {
                    postRecalcChanged[postRecalcChangedCount++] = cellIndex;
                }
                return;
            }
            const directCriteriaCurrentResultKey = directCriteriaCurrentResults === undefined || formula === undefined ? undefined : directCriteriaCurrentResultCacheKey(formula);
            const cachedDirectCriteriaResult = directCriteriaCurrentResultKey === undefined || directCriteriaCurrentResults === undefined
                ? undefined
                : directCriteriaCurrentResults.get(directCriteriaCurrentResultKey);
            if (cachedDirectCriteriaResult !== undefined && args.applyDirectFormulaCurrentResult(cellIndex, cachedDirectCriteriaResult)) {
                if (captureChanged) {
                    postRecalcChanged[postRecalcChangedCount++] = cellIndex;
                }
                return;
            }
            args.countPostRecalcDirectFormulaMetric(cellIndex, args.metrics);
            const changedCellIndices = args.evaluateDirectFormula(cellIndex);
            if (directCriteriaCurrentResultKey !== undefined) {
                const evaluatedResult = readDirectScalarCurrentOperand(args, cellIndex);
                if (evaluatedResult !== undefined) {
                    directCriteriaCurrentResults?.set(directCriteriaCurrentResultKey, evaluatedResult);
                }
            }
            if (captureChanged) {
                postRecalcChanged[postRecalcChangedCount++] = cellIndex;
            }
            if (captureChanged && changedCellIndices) {
                postRecalcExtraChanged ??= [];
                for (let index = 0; index < changedCellIndices.length; index += 1) {
                    postRecalcExtraChanged.push(changedCellIndices[index]);
                }
            }
        });
    });
    if (directAggregateDeltaApplicationCount > 0) {
        addEngineCounter(args.state.counters, 'directAggregateDeltaApplications', directAggregateDeltaApplicationCount);
    }
    if (directScalarDeltaApplicationCount > 0) {
        addEngineCounter(args.state.counters, 'directScalarDeltaApplications', directScalarDeltaApplicationCount);
    }
    if (!captureChanged) {
        return EMPTY_CHANGED_CELLS;
    }
    const directChanged = postRecalcChanged.subarray(0, postRecalcChangedCount);
    return postRecalcExtraChanged && postRecalcExtraChanged.length > 0
        ? mergeChangedCellIndices(args.recalculated, mergeChangedCellIndices(directChanged, postRecalcExtraChanged))
        : mergeChangedCellIndices(args.recalculated, directChanged);
}
function directCriteriaCurrentResultCacheKey(formula) {
    const directCriteria = formula.directCriteria;
    if (directCriteria === undefined ||
        formula.compiled.producesSpill ||
        directCriteria.aggregateKind === 'first' ||
        directCriteria.offsetOperand !== undefined ||
        directCriteria.firstMatchMode !== undefined ||
        directCriteria.resultTransforms !== undefined) {
        return undefined;
    }
    const aggregateRangeKey = directCriteria.aggregateRange === undefined ? 'none' : directCriteriaRangeKey(directCriteria.aggregateRange);
    const criteriaPairsKey = directCriteria.criteriaPairs
        .map((pair) => `${directCriteriaRangeKey(pair.range)}=${directCriteriaOperandKey(pair.criterion)}`)
        .join('\u0002');
    return `${directCriteria.aggregateKind}\u0001${aggregateRangeKey}\u0001${criteriaPairsKey}`;
}
function isActivePostRecalcDirectFormula(formula) {
    return (formula?.directAggregate !== undefined ||
        formula?.directCriteria !== undefined ||
        formula?.directLookup !== undefined ||
        formula?.directScalar !== undefined);
}
function directCriteriaRangeKey(range) {
    return `${range.sheetName}:${range.rowStart}:${range.rowEnd}:${range.col}:${range.length}`;
}
function directCriteriaOperandKey(operand) {
    switch (operand.kind) {
        case 'literal':
            return `literal:${cellValueKey(operand.value)}`;
        case 'cell':
            return `cell:${operand.cellIndex}`;
        case 'cell-string-concat':
            return `cell-string-concat:${operand.cellIndex}:${operand.prefix}:${operand.suffix}`;
        case 'cell-month-boundary-string-concat':
            return `cell-month-boundary-string-concat:${operand.cellIndex}:${operand.prefix}:${operand.suffix}:${operand.offsetMonths}`;
    }
}
function cellValueKey(value) {
    switch (value.tag) {
        case ValueTag.Empty:
            return 'empty';
        case ValueTag.Number:
            return `number:${Object.is(value.value, -0) ? 0 : value.value}`;
        case ValueTag.Boolean:
            return `boolean:${value.value ? 1 : 0}`;
        case ValueTag.String:
            return `string:${value.stringId ?? 0}:${value.value}`;
        case ValueTag.Error:
            return `error:${value.code}`;
    }
}
function readDirectScalarCurrentOperand(args, cellIndex) {
    const cellStore = args.state.workbook.cellStore;
    const tag = cellStore.tags[cellIndex];
    if (tag === ValueTag.Number) {
        return { kind: 'number', value: cellStore.numbers[cellIndex] ?? 0 };
    }
    if (tag === ValueTag.Error) {
        return { kind: 'error', code: cellStore.errors[cellIndex] ?? ErrorCode.None };
    }
    return undefined;
}
function isCycleFormulaCell(args, cellIndex) {
    return ((args.state.workbook.cellStore.flags[cellIndex] ?? 0) & 8 /* CellFlags.InCycle */) !== 0;
}
function isCycleSensitiveFormulaCell(args, cellIndex) {
    return isCycleFormulaCell(args, cellIndex) || args.hasCycleDependency?.(cellIndex) === true;
}
function hasAnyCycleSensitiveFormulaCell(args) {
    for (let index = 0; index < args.collection.size; index += 1) {
        if (isCycleSensitiveFormulaCell(args, args.collection.getCellIndexAt(index))) {
            return true;
        }
    }
    return false;
}
function countDirectDeltaApplication(args, formula) {
    if (formula?.directAggregate !== undefined || formula?.directCriteria !== undefined) {
        addEngineCounter(args.state.counters, 'directAggregateDeltaApplications');
    }
    if (formula?.directScalar !== undefined) {
        addEngineCounter(args.state.counters, 'directScalarDeltaApplications');
    }
}
//# sourceMappingURL=operation-post-recalc-direct-formulas.js.map