import { growUint32 } from '../engine-buffer-utils.js';
import { addEngineCounter } from '../perf/engine-counters.js';
const INITIAL_CALC_CHAIN_BUFFER_CAPACITY = 16;
const EMPTY_U32 = new Uint32Array(0);
export class CalcChain {
    counters;
    rankCounts = EMPTY_U32;
    formulaIds = EMPTY_U32;
    orderedChain = EMPTY_U32;
    chainIndexByCell = EMPTY_U32;
    orderedDirty = EMPTY_U32;
    dirtySeen = EMPTY_U32;
    dirtyEpoch = 1;
    chainFormulaCount = 0;
    constructor(counters) {
        this.counters = counters;
    }
    rebuild(formulaCellIndices, cellStore) {
        this.ensureCellCapacity(cellStore.size + 1);
        for (let index = 0; index < this.chainFormulaCount; index += 1) {
            const cellIndex = this.orderedChain[index];
            this.chainIndexByCell[cellIndex] = 0;
        }
        let formulaCount = 0;
        for (const cellIndex of formulaCellIndices) {
            this.ensureFormulaCapacity(formulaCount + 1);
            this.formulaIds[formulaCount] = cellIndex;
            formulaCount += 1;
        }
        this.chainFormulaCount = formulaCount;
        if (formulaCount === 0) {
            return;
        }
        let minRank = Number.MAX_SAFE_INTEGER;
        let maxRank = 0;
        for (let index = 0; index < formulaCount; index += 1) {
            const cellIndex = this.formulaIds[index];
            const rank = cellStore.topoRanks[cellIndex] ?? 0;
            minRank = Math.min(minRank, rank);
            maxRank = Math.max(maxRank, rank);
        }
        const rankSpan = maxRank - minRank + 1;
        this.ensureRankCapacity(rankSpan, formulaCount);
        this.rankCounts.fill(0, 0, rankSpan);
        for (let index = 0; index < formulaCount; index += 1) {
            const cellIndex = this.formulaIds[index];
            const rank = (cellStore.topoRanks[cellIndex] ?? 0) - minRank;
            this.rankCounts[rank] = (this.rankCounts[rank] ?? 0) + 1;
        }
        let prefix = 0;
        for (let index = 0; index < rankSpan; index += 1) {
            const count = this.rankCounts[index];
            this.rankCounts[index] = prefix;
            prefix += count;
        }
        for (let index = 0; index < formulaCount; index += 1) {
            const cellIndex = this.formulaIds[index];
            const rank = (cellStore.topoRanks[cellIndex] ?? 0) - minRank;
            const target = this.rankCounts[rank];
            this.orderedChain[target] = cellIndex;
            this.chainIndexByCell[cellIndex] = target + 1;
            this.rankCounts[rank] = target + 1;
        }
    }
    hasChainFor(formulaCount) {
        return this.chainFormulaCount === formulaCount;
    }
    coversDirty(dirtyFormulaCellIndices, dirtyFormulaCount) {
        for (let index = 0; index < dirtyFormulaCount; index += 1) {
            const cellIndex = dirtyFormulaCellIndices[index];
            if ((this.chainIndexByCell[cellIndex] ?? 0) === 0) {
                return false;
            }
        }
        return true;
    }
    orderDirty(dirtyFormulaCellIndices, dirtyFormulaCount) {
        if (dirtyFormulaCount === 0) {
            return {
                orderedFormulaCellIndices: this.orderedDirty,
                orderedFormulaCount: 0,
            };
        }
        if (dirtyFormulaCount === this.chainFormulaCount) {
            return {
                orderedFormulaCellIndices: this.orderedChain,
                orderedFormulaCount: this.chainFormulaCount,
            };
        }
        if (dirtyFormulaCount <= 64 || dirtyFormulaCount * 8 <= this.chainFormulaCount) {
            let orderedCount = 0;
            for (let index = 0; index < dirtyFormulaCount; index += 1) {
                const cellIndex = dirtyFormulaCellIndices[index];
                if ((this.chainIndexByCell[cellIndex] ?? 0) === 0) {
                    continue;
                }
                this.orderedDirty[orderedCount] = cellIndex;
                orderedCount += 1;
            }
            this.orderedDirty
                .subarray(0, orderedCount)
                .sort((left, right) => (this.chainIndexByCell[left] ?? 0) - (this.chainIndexByCell[right] ?? 0));
            return {
                orderedFormulaCellIndices: this.orderedDirty,
                orderedFormulaCount: orderedCount,
            };
        }
        this.dirtyEpoch += 1;
        if (this.dirtyEpoch === 0xffff_ffff) {
            this.dirtyEpoch = 1;
            this.dirtySeen.fill(0);
        }
        for (let index = 0; index < dirtyFormulaCount; index += 1) {
            const cellIndex = dirtyFormulaCellIndices[index];
            if ((this.chainIndexByCell[cellIndex] ?? 0) !== 0) {
                this.dirtySeen[cellIndex] = this.dirtyEpoch;
            }
        }
        let orderedCount = 0;
        if (this.counters) {
            addEngineCounter(this.counters, 'calcChainFullScans');
        }
        for (let index = 0; index < this.chainFormulaCount; index += 1) {
            const cellIndex = this.orderedChain[index];
            if (this.dirtySeen[cellIndex] !== this.dirtyEpoch) {
                continue;
            }
            this.orderedDirty[orderedCount] = cellIndex;
            orderedCount += 1;
        }
        return {
            orderedFormulaCellIndices: this.orderedDirty,
            orderedFormulaCount: orderedCount,
        };
    }
    orderAll() {
        return {
            orderedFormulaCellIndices: this.orderedChain,
            orderedFormulaCount: this.chainFormulaCount,
        };
    }
    ensureRankCapacity(rankCountSize, chainSize) {
        if (rankCountSize > this.rankCounts.length) {
            let capacity = Math.max(this.rankCounts.length, INITIAL_CALC_CHAIN_BUFFER_CAPACITY);
            while (capacity < rankCountSize) {
                capacity *= 2;
            }
            this.rankCounts = growUint32(this.rankCounts, capacity);
        }
        if (chainSize > this.orderedChain.length) {
            let capacity = Math.max(this.orderedChain.length, INITIAL_CALC_CHAIN_BUFFER_CAPACITY);
            while (capacity < chainSize) {
                capacity *= 2;
            }
            this.orderedChain = growUint32(this.orderedChain, capacity);
            this.orderedDirty = growUint32(this.orderedDirty, capacity);
        }
    }
    ensureCellCapacity(size) {
        if (size <= this.chainIndexByCell.length) {
            return;
        }
        let capacity = Math.max(this.chainIndexByCell.length, INITIAL_CALC_CHAIN_BUFFER_CAPACITY);
        while (capacity < size) {
            capacity *= 2;
        }
        this.chainIndexByCell = growUint32(this.chainIndexByCell, capacity);
        this.dirtySeen = growUint32(this.dirtySeen, capacity);
    }
    ensureFormulaCapacity(size) {
        if (size <= this.formulaIds.length) {
            return;
        }
        let capacity = Math.max(this.formulaIds.length, INITIAL_CALC_CHAIN_BUFFER_CAPACITY);
        while (capacity < size) {
            capacity *= 2;
        }
        this.formulaIds = growUint32(this.formulaIds, capacity);
        this.orderedChain = growUint32(this.orderedChain, capacity);
        this.orderedDirty = growUint32(this.orderedDirty, capacity);
    }
}
//# sourceMappingURL=calc-chain.js.map