import { ErrorCode, ValueTag } from '@bilig/protocol';
import { makeCellEntity } from '../../entity-ids.js';
import { addEngineCounter } from '../../perf/engine-counters.js';
import { markBatchApplied } from '../../replica-state.js';
import { tagTrustedPhysicalTrackedChanges } from './operation-change-helpers.js';
import { emitCellMutationFastPathBatchResult } from './operation-fast-path-batch-result.js';
const EMPTY_CHANGED_CELLS = new Uint32Array(0);
const EMPTY_NUMERIC_VALUES = new Float64Array(0);
const EMPTY_EMPTY_FLAGS = new Uint8Array(0);
export function createOperationDirectAggregateRectangularBatchFastPath(args) {
    const tryApplyDenseRectangularDirectAggregateLiteralBatch = (refs, batch, potentialNewCells) => {
        const firstRef = refs[0];
        if (firstRef === undefined || refs.length < 32 || (potentialNewCells ?? 0) !== 0) {
            return false;
        }
        const rectangle = collectDenseNumericRectangle(args, refs, firstRef);
        if (rectangle === null) {
            return false;
        }
        const { sheet, sheetId, firstRow, rowCount, firstCol, colCount, inputCellIndices, inputNumericValues, rowSums } = rectangle;
        for (let col = firstCol; col < firstCol + colCount; col += 1) {
            if (args.hasTrackedExactLookupDependents(sheetId, col) || args.hasTrackedSortedLookupDependents(sheetId, col)) {
                return false;
            }
        }
        const formulas = collectRowAggregateFormulas(args, {
            sheetName: sheet.name,
            sheetId,
            firstRow,
            rowCount,
            firstCol,
            colCount,
        });
        if (formulas === null) {
            return false;
        }
        args.materializeDeferredStructuralFormulaSources();
        const hasGeneralEventListeners = args.state.events.hasListeners();
        const hasTrackedEventListeners = args.state.events.hasTrackedListeners();
        const hasWatchedCellListeners = args.state.events.hasCellListeners();
        const requiresChangedSet = hasGeneralEventListeners || hasTrackedEventListeners || hasWatchedCellListeners;
        const changedInputCount = inputCellIndices.length;
        const explicitChangedCount = requiresChangedSet ? inputCellIndices.length : 0;
        args.setBatchMutationDepth(args.getBatchMutationDepth() + 1);
        try {
            for (let index = 0; index < inputCellIndices.length; index += 1) {
                const cellIndex = inputCellIndices[index];
                if (rectangle.allInputsClear || rectangle.emptyInputFlags[index] === 1) {
                    writeEmptyLiteralToCellStore(args.state.workbook.cellStore, cellIndex);
                }
                else {
                    args.writeNumericLiteralToCellStore(cellIndex, inputNumericValues[index]);
                }
            }
            const writtenColumns = new Uint32Array(colCount);
            for (let index = 0; index < colCount; index += 1) {
                writtenColumns[index] = firstCol + index;
            }
            args.state.workbook.notifyColumnsWritten(sheetId, writtenColumns);
        }
        finally {
            args.setBatchMutationDepth(args.getBatchMutationDepth() - 1);
        }
        if (batch) {
            markBatchApplied(args.state.replicaState, batch);
        }
        args.deferKernelSync(inputCellIndices);
        const formulaChanged = requiresChangedSet ? new Uint32Array(rowCount) : EMPTY_CHANGED_CELLS;
        for (let rowOffset = 0; rowOffset < rowCount; rowOffset += 1) {
            const formulaCellIndex = formulas.cellIndices[rowOffset];
            args.applyTerminalDirectFormulaNumericResult(formulaCellIndex, rowSums[rowOffset] + formulas.resultOffsets[rowOffset]);
            if (requiresChangedSet) {
                formulaChanged[rowOffset] = formulaCellIndex;
            }
        }
        addEngineCounter(args.state.counters, 'directAggregateDeltaApplications', rowCount);
        addEngineCounter(args.state.counters, 'directAggregateDeltaOnlyRecalcSkips');
        const changed = requiresChangedSet ? composeDenseRectangleAggregateChanges(inputCellIndices, formulaChanged) : EMPTY_CHANGED_CELLS;
        if (hasTrackedEventListeners && changed.length > 4 && explicitChangedCount > 0 && explicitChangedCount < changed.length) {
            tagTrustedPhysicalTrackedChanges(changed, sheetId, explicitChangedCount);
        }
        emitCellMutationFastPathBatchResult({
            state: args.state,
            changed,
            changedInputCount,
            explicitChangedCount,
            hasGeneralEventListeners,
            hasTrackedEventListeners,
            hasWatchedCellListeners,
            captureChangedCells: args.captureChangedCells,
            batch,
            emitBatch: args.emitBatch,
        });
        return true;
    };
    return { tryApplyDenseRectangularDirectAggregateLiteralBatch };
}
function composeDenseRectangleAggregateChanges(inputCellIndices, formulaCellIndices) {
    const changed = new Uint32Array(inputCellIndices.length + formulaCellIndices.length);
    changed.set(inputCellIndices);
    changed.set(formulaCellIndices, inputCellIndices.length);
    return changed;
}
function collectDenseNumericRectangle(args, refs, firstRef) {
    const firstMutation = firstRef.mutation;
    if (!isSupportedAggregateBatchMutation(firstMutation)) {
        return null;
    }
    const sheet = args.state.workbook.getSheetById(firstRef.sheetId);
    if (!sheet || sheet.structureVersion !== 1) {
        return null;
    }
    const inputCellIndices = new Uint32Array(refs.length);
    let inputNumericValues;
    let emptyInputFlags;
    let rowSumsScratch;
    let allInputsClear = true;
    const cellStore = args.state.workbook.cellStore;
    const firstRow = firstMutation.row;
    const firstCol = firstMutation.col;
    let currentRow = firstRow;
    let currentWidth = 0;
    let colCount = 0;
    let rowOffset = 0;
    for (let refIndex = 0; refIndex < refs.length; refIndex += 1) {
        const ref = refs[refIndex];
        const mutation = ref.mutation;
        if (ref.sheetId !== firstRef.sheetId || !isSupportedAggregateBatchMutation(mutation)) {
            return null;
        }
        if (mutation.row === currentRow) {
            if (mutation.col !== firstCol + currentWidth) {
                return null;
            }
            currentWidth += 1;
        }
        else {
            if (mutation.row !== currentRow + 1 || mutation.col !== firstCol || currentWidth === 0) {
                return null;
            }
            if (colCount === 0) {
                colCount = currentWidth;
            }
            else if (currentWidth !== colCount) {
                return null;
            }
            currentRow = mutation.row;
            currentWidth = 1;
            rowOffset += 1;
        }
        const existingIndex = ref.cellIndex !== undefined &&
            cellStore.sheetIds[ref.cellIndex] === ref.sheetId &&
            cellStore.rows[ref.cellIndex] === mutation.row &&
            cellStore.cols[ref.cellIndex] === mutation.col
            ? ref.cellIndex
            : sheet.grid.getPhysical(mutation.row, mutation.col);
        if (existingIndex === -1 ||
            !args.canFastPathLiteralOverwrite(existingIndex) ||
            args.getSingleEntityDependent(makeCellEntity(existingIndex)) !== -1) {
            return null;
        }
        inputCellIndices[refIndex] = existingIndex;
        if (mutation.kind === 'clearCell' || mutation.value === null) {
            if (!allInputsClear) {
                emptyInputFlags[refIndex] = 1;
            }
        }
        else {
            const numericValue = mutation.value;
            if (typeof numericValue !== 'number') {
                return null;
            }
            if (allInputsClear) {
                allInputsClear = false;
                inputNumericValues = new Float64Array(refs.length);
                emptyInputFlags = new Uint8Array(refs.length);
                emptyInputFlags.fill(1, 0, refIndex);
                rowSumsScratch = new Float64Array(refs.length);
            }
            inputNumericValues[refIndex] = numericValue;
            rowSumsScratch[rowOffset] = (rowSumsScratch[rowOffset] ?? 0) + numericValue;
        }
    }
    if (colCount === 0) {
        colCount = currentWidth;
    }
    else if (currentWidth !== colCount) {
        return null;
    }
    const rowCount = rowOffset + 1;
    if (rowCount < 2 || colCount < 2 || rowCount * colCount !== refs.length) {
        return null;
    }
    return {
        sheet,
        sheetId: firstRef.sheetId,
        firstRow,
        rowCount,
        firstCol,
        colCount,
        inputCellIndices,
        inputNumericValues: inputNumericValues ?? EMPTY_NUMERIC_VALUES,
        emptyInputFlags: emptyInputFlags ?? EMPTY_EMPTY_FLAGS,
        allInputsClear,
        rowSums: allInputsClear ? new Float64Array(rowCount) : rowSumsScratch.subarray(0, rowCount),
    };
}
function isSupportedAggregateBatchMutation(mutation) {
    if (mutation.kind === 'clearCell') {
        return true;
    }
    return (mutation.kind === 'setCellValue' && (mutation.value === null || (typeof mutation.value === 'number' && !Object.is(mutation.value, -0))));
}
function writeEmptyLiteralToCellStore(cellStore, cellIndex) {
    const flags = cellStore.flags[cellIndex] ?? 0;
    cellStore.flags[cellIndex] = flags & ~(256 /* CellFlags.AuthoredBlank */ | 64 /* CellFlags.SpillChild */ | 128 /* CellFlags.PivotOutput */);
    cellStore.versions[cellIndex] = (cellStore.versions[cellIndex] ?? 0) + 1;
    cellStore.stringIds[cellIndex] = 0;
    cellStore.tags[cellIndex] = ValueTag.Empty;
    cellStore.numbers[cellIndex] = 0;
    cellStore.errors[cellIndex] = ErrorCode.None;
}
function collectRowAggregateFormulas(args, rectangle) {
    const cellIndices = new Int32Array(rectangle.rowCount);
    cellIndices.fill(-1);
    const resultOffsets = new Float64Array(rectangle.rowCount);
    let invalid = false;
    const rowEnd = rectangle.firstRow + rectangle.rowCount - 1;
    const colEnd = rectangle.firstCol + rectangle.colCount - 1;
    args.state.formulas.forEach((formula, cellIndex) => {
        if (invalid) {
            return;
        }
        if (formula.directCriteria !== undefined &&
            directCriteriaOverlapsRectangle(formula.directCriteria, rectangle.sheetName, rectangle.firstRow, rowEnd, rectangle.firstCol, colEnd)) {
            invalid = true;
            return;
        }
        const aggregate = formula.directAggregate;
        if (aggregate === undefined ||
            !directAggregateOverlapsRectangle(aggregate, rectangle.sheetName, rectangle.firstRow, rowEnd, rectangle.firstCol, colEnd)) {
            return;
        }
        if (aggregate.aggregateKind !== 'sum' ||
            aggregate.sheetName !== rectangle.sheetName ||
            aggregate.rowStart !== aggregate.rowEnd ||
            aggregate.rowStart < rectangle.firstRow ||
            aggregate.rowStart > rowEnd ||
            aggregate.col !== rectangle.firstCol ||
            aggregate.colEnd !== colEnd ||
            formula.dependencyIndices.length !== 0 ||
            formula.directCriteria !== undefined ||
            formula.directLookup !== undefined ||
            formula.directScalar !== undefined ||
            !args.canUseDirectFormulaPostRecalc(cellIndex) ||
            !args.canSkipFormulaColumnVersion(cellIndex) ||
            ((args.state.workbook.cellStore.flags[cellIndex] ?? 0) & 8 /* CellFlags.InCycle */) !== 0) {
            invalid = true;
            return;
        }
        const formulaSheetId = args.state.workbook.cellStore.sheetIds[cellIndex];
        const formulaRow = args.state.workbook.cellStore.rows[cellIndex];
        const formulaCol = args.state.workbook.cellStore.cols[cellIndex];
        if (formulaSheetId !== rectangle.sheetId ||
            formulaRow !== aggregate.rowStart ||
            formulaCol === undefined ||
            (formulaCol >= rectangle.firstCol && formulaCol <= colEnd)) {
            invalid = true;
            return;
        }
        const rowOffset = aggregate.rowStart - rectangle.firstRow;
        if (cellIndices[rowOffset] !== -1) {
            invalid = true;
            return;
        }
        cellIndices[rowOffset] = cellIndex;
        resultOffsets[rowOffset] = aggregate.resultOffset ?? 0;
    });
    if (invalid) {
        return null;
    }
    const unsignedCellIndices = new Uint32Array(rectangle.rowCount);
    for (let index = 0; index < rectangle.rowCount; index += 1) {
        const cellIndex = cellIndices[index];
        if (cellIndex < 0) {
            return null;
        }
        unsignedCellIndices[index] = cellIndex;
    }
    return { cellIndices: unsignedCellIndices, resultOffsets };
}
function directAggregateOverlapsRectangle(aggregate, sheetName, rowStart, rowEnd, colStart, colEnd) {
    return (aggregate.sheetName === sheetName &&
        aggregate.rowEnd >= rowStart &&
        aggregate.rowStart <= rowEnd &&
        aggregate.colEnd >= colStart &&
        aggregate.col <= colEnd);
}
function directCriteriaOverlapsRectangle(criteria, sheetName, rowStart, rowEnd, colStart, colEnd) {
    const rangeOverlaps = (range) => range.sheetName === sheetName && range.rowEnd >= rowStart && range.rowStart <= rowEnd && range.col >= colStart && range.col <= colEnd;
    if (criteria.aggregateRange && rangeOverlaps(criteria.aggregateRange)) {
        return true;
    }
    return criteria.criteriaPairs.some((pair) => rangeOverlaps(pair.range));
}
//# sourceMappingURL=operation-direct-aggregate-rectangular-batch-fast-path.js.map