import { MAX_COLS } from '@bilig/protocol';
import { addEngineCounter } from '../perf/engine-counters.js';
import { createRegionNodeStore } from './region-node-store.js';
import { buildIntervalTree, collectIntervalsContainingRow, } from './region-graph-interval-tree.js';
const DIRTY_LINEAR_POINT_QUERY_LIMIT = 2;
const DIRTY_LINEAR_REGION_LIMIT = 2_048;
const MULTIPLE_POINT_DEPENDENTS = -2;
const POINT_IMPACT_MAX_REGION_LENGTH = 256;
const POINT_IMPACT_MAX_CELLS = 65_536;
const POINT_IMPACT_MAX_ROW_INDEX = 131_072;
const DEPENDENT_DEDUP_SET_THRESHOLD = 32;
const EMPTY_U32 = new Uint32Array(0);
function columnKey(sheetId, col) {
    return sheetId * MAX_COLS + col;
}
function sheetIdFromColumnKey(key) {
    return Math.floor(key / MAX_COLS);
}
function colFromColumnKey(key) {
    return key % MAX_COLS;
}
function collectRegionIdsContainingRow(regionIds, row, nodeStore, target, orderedByRowStart) {
    for (const regionId of regionIds) {
        const region = nodeStore.get(regionId);
        if (!region) {
            continue;
        }
        if (orderedByRowStart && region.rowStart > row) {
            break;
        }
        if (region.rowStart <= row && row <= region.rowEnd) {
            target.push(regionId);
        }
    }
}
function pushUniqueDependent(dependents, formulaCellIndex) {
    for (let index = 0; index < dependents.length; index += 1) {
        if (dependents[index] === formulaCellIndex) {
            return;
        }
    }
    dependents.push(formulaCellIndex);
}
function pushAdaptiveUniqueDependent(dependents, seenDependents, formulaCellIndex) {
    if (seenDependents) {
        if (!seenDependents.has(formulaCellIndex)) {
            seenDependents.add(formulaCellIndex);
            dependents.push(formulaCellIndex);
        }
        return seenDependents;
    }
    pushUniqueDependent(dependents, formulaCellIndex);
    return dependents.length >= DEPENDENT_DEDUP_SET_THRESHOLD ? new Set(dependents) : undefined;
}
function mergeSingleDependent(current, next) {
    if (current === MULTIPLE_POINT_DEPENDENTS || next === MULTIPLE_POINT_DEPENDENTS) {
        return MULTIPLE_POINT_DEPENDENTS;
    }
    if (current === -1) {
        return next;
    }
    return current === next ? current : MULTIPLE_POINT_DEPENDENTS;
}
function mergePointImpactDependent(current, next) {
    if (current === undefined || current === next) {
        return next;
    }
    return MULTIPLE_POINT_DEPENDENTS;
}
function forEachFormulaRegionSubscription(subscription, fn) {
    if (typeof subscription === 'number') {
        fn(subscription);
        return;
    }
    for (let index = 0; index < subscription.length; index += 1) {
        fn(subscription[index]);
    }
}
function forEachRegionSubscriber(subscription, fn) {
    if (typeof subscription === 'number') {
        fn(subscription);
        return;
    }
    subscription.forEach(fn);
}
function regionSubscriberSize(subscription) {
    return typeof subscription === 'number' ? 1 : subscription.size;
}
function regionSubscribersToUint32(subscription) {
    if (subscription === undefined) {
        return EMPTY_U32;
    }
    return typeof subscription === 'number' ? Uint32Array.of(subscription) : Uint32Array.from(subscription);
}
function formulaRegionSubscriptionToArray(subscription) {
    if (subscription === undefined) {
        return [];
    }
    return typeof subscription === 'number' ? [subscription] : subscription;
}
function pushUniqueRegionId(regionIds, regionId) {
    for (let index = 0; index < regionIds.length; index += 1) {
        if (regionIds[index] === regionId) {
            return false;
        }
    }
    regionIds.push(regionId);
    return true;
}
function collectSingleDependentForRegionIds(regionIds, row, nodeStore, regionSubscribers, orderedByRowStart) {
    let singleDependent = -1;
    const mergeRegion = (regionId) => {
        const region = nodeStore.get(regionId);
        if (!region || region.rowStart > row || region.rowEnd < row) {
            return;
        }
        const subscribers = regionSubscribers.get(regionId);
        if (subscribers === undefined || regionSubscriberSize(subscribers) === 0) {
            return;
        }
        if (regionSubscriberSize(subscribers) !== 1) {
            singleDependent = -2;
            return;
        }
        forEachRegionSubscriber(subscribers, (formulaCellIndex) => {
            singleDependent = mergeSingleDependent(singleDependent, formulaCellIndex);
        });
    };
    for (const regionId of regionIds) {
        if (orderedByRowStart) {
            const region = nodeStore.get(regionId);
            if (region && region.rowStart > row) {
                break;
            }
        }
        mergeRegion(regionId);
        if (singleDependent === -2) {
            return singleDependent;
        }
    }
    return singleDependent;
}
function disablePointImpactIndex(subscriptions) {
    subscriptions.pointImpactIndex = undefined;
    subscriptions.pointImpactCellCount = 0;
    subscriptions.pointImpactDirty = false;
    subscriptions.pointImpactDisabled = true;
}
function markPointImpactIndexDirty(subscriptions) {
    if (subscriptions.pointImpactIndex !== undefined) {
        subscriptions.pointImpactDirty = true;
    }
}
function addSingleSubscriberPointImpacts(subscriptions, region, formulaCellIndex) {
    if (subscriptions.pointImpactDisabled || subscriptions.pointImpactDirty) {
        return;
    }
    const regionLength = region.rowEnd - region.rowStart + 1;
    if (regionLength <= 0 ||
        region.rowEnd > POINT_IMPACT_MAX_ROW_INDEX ||
        regionLength > POINT_IMPACT_MAX_REGION_LENGTH ||
        subscriptions.pointImpactCellCount + regionLength > POINT_IMPACT_MAX_CELLS) {
        disablePointImpactIndex(subscriptions);
        return;
    }
    const index = (subscriptions.pointImpactIndex ??= []);
    for (let row = region.rowStart; row <= region.rowEnd; row += 1) {
        index[row] = mergePointImpactDependent(index[row], formulaCellIndex);
    }
    subscriptions.pointImpactCellCount += regionLength;
}
function collectSingleDependentFromPointImpactIndex(subscriptions, row) {
    if (subscriptions.pointImpactDirty || subscriptions.pointImpactDisabled || subscriptions.pointImpactIndex === undefined) {
        return undefined;
    }
    return subscriptions.pointImpactIndex[row] ?? -1;
}
function collectSingleDependentForRow(node, row, regionSubscribers) {
    let singleDependent = -1;
    const mergeRegion = (regionId) => {
        const subscribers = regionSubscribers.get(regionId);
        if (subscribers === undefined || regionSubscriberSize(subscribers) === 0) {
            return;
        }
        if (regionSubscriberSize(subscribers) !== 1) {
            singleDependent = -2;
            return;
        }
        forEachRegionSubscriber(subscribers, (formulaCellIndex) => {
            singleDependent = mergeSingleDependent(singleDependent, formulaCellIndex);
        });
    };
    const visit = (current) => {
        if (!current || singleDependent === -2) {
            return;
        }
        if (row < current.center) {
            for (let index = 0; index < current.overlappingByStart.length; index += 1) {
                const interval = current.overlappingByStart[index];
                if (interval.rowStart > row) {
                    break;
                }
                mergeRegion(interval.regionId);
                if (singleDependent === -2) {
                    return;
                }
            }
            visit(current.left);
            return;
        }
        if (row > current.center) {
            for (let index = 0; index < current.overlappingByEnd.length; index += 1) {
                const interval = current.overlappingByEnd[index];
                if (interval.rowEnd < row) {
                    break;
                }
                mergeRegion(interval.regionId);
                if (singleDependent === -2) {
                    return;
                }
            }
            visit(current.right);
            return;
        }
        for (let index = 0; index < current.overlappingByStart.length; index += 1) {
            mergeRegion(current.overlappingByStart[index].regionId);
            if (singleDependent === -2) {
                return;
            }
        }
    };
    visit(node);
    return singleDependent;
}
function rowCanIntersectSubscriptions(subscriptions, row) {
    return subscriptions.regionIds.size > 0 && row >= subscriptions.minRowStart && row <= subscriptions.maxRowEnd;
}
function addColumnRegionBounds(subscriptions, region) {
    if (region.rowStart < subscriptions.minRowStart) {
        subscriptions.minRowStart = region.rowStart;
        subscriptions.minRowStartCount = 1;
    }
    else if (region.rowStart === subscriptions.minRowStart) {
        subscriptions.minRowStartCount += 1;
    }
    if (region.rowEnd > subscriptions.maxRowEnd) {
        subscriptions.maxRowEnd = region.rowEnd;
        subscriptions.maxRowEndCount = 1;
    }
    else if (region.rowEnd === subscriptions.maxRowEnd) {
        subscriptions.maxRowEndCount += 1;
    }
}
function resetColumnSubscriptionSummary(subscriptions) {
    subscriptions.orderedByRowStart = true;
    subscriptions.lastRowStart = Number.NEGATIVE_INFINITY;
    subscriptions.minRowStart = Number.POSITIVE_INFINITY;
    subscriptions.minRowStartCount = 0;
    subscriptions.maxRowEnd = Number.NEGATIVE_INFINITY;
    subscriptions.maxRowEndCount = 0;
}
export function createRegionGraph(args) {
    const nodeStore = args.nodeStore ?? createRegionNodeStore();
    const regionSubscribers = new Map();
    const formulaRegions = new Map();
    const columnSubscriptions = new Map();
    const dirtyColumns = new Set();
    const getColumnSubscriptions = (sheetId, col) => {
        const key = columnKey(sheetId, col);
        const existing = columnSubscriptions.get(key);
        if (existing) {
            return existing;
        }
        const created = {
            regionIds: new Set(),
            tree: undefined,
            dirty: false,
            dirtyPointQueries: 0,
            orderedByRowStart: true,
            lastRowStart: Number.NEGATIVE_INFINITY,
            minRowStart: Number.POSITIVE_INFINITY,
            minRowStartCount: 0,
            maxRowEnd: Number.NEGATIVE_INFINITY,
            maxRowEndCount: 0,
            pointImpactIndex: undefined,
            pointImpactCellCount: 0,
            pointImpactDirty: false,
            pointImpactDisabled: false,
        };
        columnSubscriptions.set(key, created);
        return created;
    };
    const recomputeColumnRowBounds = (subscriptions) => {
        subscriptions.minRowStart = Number.POSITIVE_INFINITY;
        subscriptions.minRowStartCount = 0;
        subscriptions.maxRowEnd = Number.NEGATIVE_INFINITY;
        subscriptions.maxRowEndCount = 0;
        subscriptions.regionIds.forEach((regionId) => {
            const region = nodeStore.get(regionId);
            if (!region) {
                return;
            }
            addColumnRegionBounds(subscriptions, region);
        });
    };
    const recomputeColumnSubscriptionSummary = (subscriptions) => {
        resetColumnSubscriptionSummary(subscriptions);
        subscriptions.regionIds.forEach((regionId) => {
            const region = nodeStore.get(regionId);
            if (!region) {
                return;
            }
            if (region.rowStart < subscriptions.lastRowStart) {
                subscriptions.orderedByRowStart = false;
            }
            subscriptions.lastRowStart = Math.max(subscriptions.lastRowStart, region.rowStart);
            addColumnRegionBounds(subscriptions, region);
        });
    };
    const markColumnDirty = (regionId) => {
        const region = nodeStore.get(regionId);
        if (!region) {
            return;
        }
        const key = columnKey(region.sheetId, region.col);
        const subscriptions = getColumnSubscriptions(region.sheetId, region.col);
        subscriptions.dirty = true;
        subscriptions.dirtyPointQueries = 0;
        dirtyColumns.add(key);
    };
    const rebuildPointImpactIndex = (subscriptions) => {
        if (subscriptions.pointImpactDisabled) {
            return false;
        }
        const nextIndex = [];
        let nextCellCount = 0;
        for (const regionId of subscriptions.regionIds) {
            const region = nodeStore.get(regionId);
            if (!region) {
                continue;
            }
            const regionLength = region.rowEnd - region.rowStart + 1;
            if (regionLength <= 0 ||
                region.rowEnd > POINT_IMPACT_MAX_ROW_INDEX ||
                regionLength > POINT_IMPACT_MAX_REGION_LENGTH ||
                nextCellCount + regionLength > POINT_IMPACT_MAX_CELLS) {
                disablePointImpactIndex(subscriptions);
                return false;
            }
            const subscribers = regionSubscribers.get(regionId);
            if (subscribers === undefined) {
                continue;
            }
            for (let row = region.rowStart; row <= region.rowEnd; row += 1) {
                forEachRegionSubscriber(subscribers, (formulaCellIndex) => {
                    nextIndex[row] = mergePointImpactDependent(nextIndex[row], formulaCellIndex);
                });
            }
            nextCellCount += regionLength;
        }
        subscriptions.pointImpactIndex = nextIndex;
        subscriptions.pointImpactCellCount = nextCellCount;
        subscriptions.pointImpactDirty = false;
        return true;
    };
    const collectSingleDependentFromPointImpact = (subscriptions, row) => {
        const indexed = collectSingleDependentFromPointImpactIndex(subscriptions, row);
        if (indexed !== undefined) {
            return indexed;
        }
        return rebuildPointImpactIndex(subscriptions) ? collectSingleDependentFromPointImpactIndex(subscriptions, row) : undefined;
    };
    const addRegionSubscription = (formulaCellIndex, regionId) => {
        const subscribers = regionSubscribers.get(regionId);
        const wasEmpty = subscribers === undefined;
        if (subscribers === undefined) {
            regionSubscribers.set(regionId, formulaCellIndex);
        }
        else if (typeof subscribers === 'number') {
            if (subscribers !== formulaCellIndex) {
                regionSubscribers.set(regionId, new Set([subscribers, formulaCellIndex]));
                const region = nodeStore.get(regionId);
                if (region) {
                    markPointImpactIndexDirty(getColumnSubscriptions(region.sheetId, region.col));
                }
            }
        }
        else {
            const previousSize = subscribers.size;
            subscribers.add(formulaCellIndex);
            if (subscribers.size !== previousSize) {
                const region = nodeStore.get(regionId);
                if (region) {
                    markPointImpactIndexDirty(getColumnSubscriptions(region.sheetId, region.col));
                }
            }
        }
        if (wasEmpty) {
            const region = nodeStore.get(regionId);
            if (region) {
                const subscriptions = getColumnSubscriptions(region.sheetId, region.col);
                if (region.rowStart < subscriptions.lastRowStart) {
                    subscriptions.orderedByRowStart = false;
                }
                subscriptions.lastRowStart = Math.max(subscriptions.lastRowStart, region.rowStart);
                addColumnRegionBounds(subscriptions, region);
                subscriptions.regionIds.add(regionId);
                addSingleSubscriberPointImpacts(subscriptions, region, formulaCellIndex);
                markColumnDirty(regionId);
            }
        }
    };
    const removeRegionSubscription = (formulaCellIndex, regionId) => {
        const subscribers = regionSubscribers.get(regionId);
        if (subscribers === undefined) {
            return;
        }
        let removedSubscriber = false;
        let shouldRemoveRegion = false;
        if (typeof subscribers === 'number') {
            if (subscribers !== formulaCellIndex) {
                return;
            }
            removedSubscriber = true;
            shouldRemoveRegion = true;
            regionSubscribers.delete(regionId);
        }
        else {
            const previousSize = subscribers.size;
            subscribers.delete(formulaCellIndex);
            if (subscribers.size === previousSize) {
                return;
            }
            removedSubscriber = true;
            if (subscribers.size > 1) {
                shouldRemoveRegion = false;
            }
            else if (subscribers.size === 1) {
                for (const remaining of subscribers) {
                    regionSubscribers.set(regionId, remaining);
                }
                shouldRemoveRegion = false;
            }
            else {
                shouldRemoveRegion = true;
                regionSubscribers.delete(regionId);
            }
        }
        const region = nodeStore.get(regionId);
        if (!region) {
            return;
        }
        const subscriptions = getColumnSubscriptions(region.sheetId, region.col);
        if (removedSubscriber) {
            markPointImpactIndexDirty(subscriptions);
        }
        if (!shouldRemoveRegion) {
            return;
        }
        subscriptions.regionIds.delete(regionId);
        if (subscriptions.regionIds.size === 0) {
            subscriptions.orderedByRowStart = true;
            subscriptions.lastRowStart = Number.NEGATIVE_INFINITY;
            subscriptions.minRowStart = Number.POSITIVE_INFINITY;
            subscriptions.minRowStartCount = 0;
            subscriptions.maxRowEnd = Number.NEGATIVE_INFINITY;
            subscriptions.maxRowEndCount = 0;
            subscriptions.pointImpactIndex = undefined;
            subscriptions.pointImpactCellCount = 0;
            subscriptions.pointImpactDirty = false;
            subscriptions.pointImpactDisabled = false;
        }
        else {
            let shouldRecomputeBounds = false;
            if (region.rowStart === subscriptions.minRowStart) {
                subscriptions.minRowStartCount -= 1;
                shouldRecomputeBounds = subscriptions.minRowStartCount <= 0;
            }
            if (region.rowEnd === subscriptions.maxRowEnd) {
                subscriptions.maxRowEndCount -= 1;
                shouldRecomputeBounds = shouldRecomputeBounds || subscriptions.maxRowEndCount <= 0;
            }
            if (shouldRecomputeBounds) {
                recomputeColumnRowBounds(subscriptions);
            }
        }
        markColumnDirty(regionId);
    };
    const touchBatchedRegionColumn = (touchedColumnKeys, regionId) => {
        const region = nodeStore.get(regionId);
        if (!region) {
            return undefined;
        }
        touchedColumnKeys.add(columnKey(region.sheetId, region.col));
        return {
            region,
            subscriptions: getColumnSubscriptions(region.sheetId, region.col),
        };
    };
    const removeRegionSubscriptionBatched = (touchedColumnKeys, formulaCellIndex, regionId) => {
        const subscribers = regionSubscribers.get(regionId);
        if (subscribers === undefined) {
            return;
        }
        let removedSubscriber = false;
        let shouldRemoveRegion = false;
        if (typeof subscribers === 'number') {
            if (subscribers !== formulaCellIndex) {
                return;
            }
            removedSubscriber = true;
            shouldRemoveRegion = true;
            regionSubscribers.delete(regionId);
        }
        else {
            const previousSize = subscribers.size;
            subscribers.delete(formulaCellIndex);
            if (subscribers.size === previousSize) {
                return;
            }
            removedSubscriber = true;
            if (subscribers.size > 1) {
                shouldRemoveRegion = false;
            }
            else if (subscribers.size === 1) {
                for (const remaining of subscribers) {
                    regionSubscribers.set(regionId, remaining);
                }
                shouldRemoveRegion = false;
            }
            else {
                shouldRemoveRegion = true;
                regionSubscribers.delete(regionId);
            }
        }
        const touched = touchBatchedRegionColumn(touchedColumnKeys, regionId);
        if (!touched) {
            return;
        }
        if (removedSubscriber) {
            markPointImpactIndexDirty(touched.subscriptions);
        }
        if (shouldRemoveRegion) {
            touched.subscriptions.regionIds.delete(regionId);
        }
    };
    const addRegionSubscriptionBatched = (touchedColumnKeys, formulaCellIndex, regionId) => {
        const subscribers = regionSubscribers.get(regionId);
        const wasEmpty = subscribers === undefined;
        if (subscribers === undefined) {
            regionSubscribers.set(regionId, formulaCellIndex);
        }
        else if (typeof subscribers === 'number') {
            if (subscribers !== formulaCellIndex) {
                regionSubscribers.set(regionId, new Set([subscribers, formulaCellIndex]));
                const touched = touchBatchedRegionColumn(touchedColumnKeys, regionId);
                if (touched) {
                    markPointImpactIndexDirty(touched.subscriptions);
                }
            }
        }
        else {
            const previousSize = subscribers.size;
            subscribers.add(formulaCellIndex);
            if (subscribers.size !== previousSize) {
                const touched = touchBatchedRegionColumn(touchedColumnKeys, regionId);
                if (touched) {
                    markPointImpactIndexDirty(touched.subscriptions);
                }
            }
        }
        if (wasEmpty) {
            const touched = touchBatchedRegionColumn(touchedColumnKeys, regionId);
            if (touched) {
                touched.subscriptions.regionIds.add(regionId);
                markPointImpactIndexDirty(touched.subscriptions);
            }
        }
    };
    const finishBatchedRegionSubscriptionReplacements = (touchedColumnKeys) => {
        touchedColumnKeys.forEach((key) => {
            const subscriptions = columnSubscriptions.get(key);
            if (!subscriptions) {
                return;
            }
            if (subscriptions.regionIds.size === 0) {
                resetColumnSubscriptionSummary(subscriptions);
                subscriptions.pointImpactIndex = undefined;
                subscriptions.pointImpactCellCount = 0;
                subscriptions.pointImpactDirty = false;
                subscriptions.pointImpactDisabled = false;
            }
            else {
                recomputeColumnSubscriptionSummary(subscriptions);
            }
            subscriptions.dirty = true;
            subscriptions.dirtyPointQueries = 0;
            dirtyColumns.add(key);
        });
    };
    const ensureIntervalTree = (sheetId, col) => {
        const key = columnKey(sheetId, col);
        const subscriptions = getColumnSubscriptions(sheetId, col);
        if (!subscriptions.dirty) {
            return subscriptions.tree;
        }
        const intervals = [];
        subscriptions.regionIds.forEach((regionId) => {
            const region = nodeStore.get(regionId);
            if (!region) {
                return;
            }
            intervals.push({
                regionId,
                rowStart: region.rowStart,
                rowEnd: region.rowEnd,
            });
        });
        if (args.counters) {
            addEngineCounter(args.counters, 'regionQueryIndexBuilds');
        }
        subscriptions.tree = buildIntervalTree(intervals);
        subscriptions.dirty = false;
        dirtyColumns.delete(key);
        return subscriptions.tree;
    };
    return {
        internSingleColumnRegion({ sheetName, rowStart, rowEnd, col }) {
            const sheet = args.workbook.getSheet(sheetName);
            if (!sheet) {
                throw new Error(`Unknown sheet for region graph: ${sheetName}`);
            }
            return nodeStore.internSingleColumnRegion({
                sheetId: sheet.id,
                sheetName,
                rowStart,
                rowEnd,
                col,
            });
        },
        getRegion(regionId) {
            return nodeStore.get(regionId);
        },
        replaceFormulaSubscriptions(formulaCellIndex, regionIds) {
            const previous = formulaRegions.get(formulaCellIndex);
            if (previous !== undefined) {
                forEachFormulaRegionSubscription(previous, (regionId) => {
                    removeRegionSubscription(formulaCellIndex, regionId);
                });
            }
            if (regionIds.length === 0) {
                formulaRegions.delete(formulaCellIndex);
                return;
            }
            if (regionIds.length === 1) {
                const regionId = regionIds[0];
                addRegionSubscription(formulaCellIndex, regionId);
                formulaRegions.set(formulaCellIndex, regionId);
                return;
            }
            const next = [];
            for (let index = 0; index < regionIds.length; index += 1) {
                const regionId = regionIds[index];
                if (!pushUniqueRegionId(next, regionId)) {
                    continue;
                }
                addRegionSubscription(formulaCellIndex, regionId);
            }
            formulaRegions.set(formulaCellIndex, next);
        },
        replaceSingleFormulaSubscription(formulaCellIndex, previousRegionId, nextRegionId) {
            if (previousRegionId === nextRegionId) {
                return;
            }
            const previous = formulaRegions.get(formulaCellIndex);
            if (previous === previousRegionId) {
                removeRegionSubscription(formulaCellIndex, previousRegionId);
                addRegionSubscription(formulaCellIndex, nextRegionId);
                formulaRegions.set(formulaCellIndex, nextRegionId);
                return;
            }
            if (previous === undefined || typeof previous === 'number' || previous.length !== 1 || previous[0] !== previousRegionId) {
                if (previous !== undefined) {
                    forEachFormulaRegionSubscription(previous, (regionId) => {
                        removeRegionSubscription(formulaCellIndex, regionId);
                    });
                }
                addRegionSubscription(formulaCellIndex, nextRegionId);
                formulaRegions.set(formulaCellIndex, nextRegionId);
                return;
            }
            removeRegionSubscription(formulaCellIndex, previousRegionId);
            addRegionSubscription(formulaCellIndex, nextRegionId);
            formulaRegions.set(formulaCellIndex, [nextRegionId]);
        },
        replaceSingleFormulaSubscriptions(replacements) {
            if (replacements.length === 0) {
                return;
            }
            const touchedColumnKeys = new Set();
            for (let index = 0; index < replacements.length; index += 1) {
                const { formulaCellIndex, previousRegionId, nextRegionId } = replacements[index];
                if (previousRegionId === nextRegionId) {
                    continue;
                }
                const previous = formulaRegions.get(formulaCellIndex);
                if (previous === undefined ||
                    (typeof previous !== 'number' && (previous.length !== 1 || previous[0] !== previousRegionId)) ||
                    (typeof previous === 'number' && previous !== previousRegionId)) {
                    if (previous !== undefined) {
                        forEachFormulaRegionSubscription(previous, (regionId) => {
                            removeRegionSubscriptionBatched(touchedColumnKeys, formulaCellIndex, regionId);
                        });
                    }
                    addRegionSubscriptionBatched(touchedColumnKeys, formulaCellIndex, nextRegionId);
                    formulaRegions.set(formulaCellIndex, nextRegionId);
                    continue;
                }
                removeRegionSubscriptionBatched(touchedColumnKeys, formulaCellIndex, previousRegionId);
                addRegionSubscriptionBatched(touchedColumnKeys, formulaCellIndex, nextRegionId);
                formulaRegions.set(formulaCellIndex, nextRegionId);
            }
            finishBatchedRegionSubscriptionReplacements(touchedColumnKeys);
        },
        clearFormulaSubscriptions(formulaCellIndex) {
            const previous = formulaRegions.get(formulaCellIndex);
            if (previous === undefined) {
                return;
            }
            forEachFormulaRegionSubscription(previous, (regionId) => {
                removeRegionSubscription(formulaCellIndex, regionId);
            });
            formulaRegions.delete(formulaCellIndex);
        },
        getFormulaSubscriptions(formulaCellIndex) {
            return formulaRegionSubscriptionToArray(formulaRegions.get(formulaCellIndex));
        },
        prepareQueryIndices() {
            dirtyColumns.forEach((key) => {
                ensureIntervalTree(sheetIdFromColumnKey(key), colFromColumnKey(key));
            });
        },
        collectFormulaDependentsForCell(sheetId, row, col) {
            const matchingRegions = [];
            const subscriptions = getColumnSubscriptions(sheetId, col);
            if (!rowCanIntersectSubscriptions(subscriptions, row)) {
                return EMPTY_U32;
            }
            if (subscriptions.dirty &&
                subscriptions.dirtyPointQueries < DIRTY_LINEAR_POINT_QUERY_LIMIT &&
                subscriptions.regionIds.size <= DIRTY_LINEAR_REGION_LIMIT) {
                subscriptions.dirtyPointQueries += 1;
                collectRegionIdsContainingRow(subscriptions.regionIds, row, nodeStore, matchingRegions, subscriptions.orderedByRowStart);
            }
            else {
                collectIntervalsContainingRow(ensureIntervalTree(sheetId, col), row, matchingRegions);
            }
            if (matchingRegions.length === 0) {
                return EMPTY_U32;
            }
            if (matchingRegions.length === 1) {
                const subscribers = regionSubscribers.get(matchingRegions[0]);
                return regionSubscribersToUint32(subscribers);
            }
            const dependents = [];
            let seenDependents;
            for (let regionIndex = 0; regionIndex < matchingRegions.length; regionIndex += 1) {
                const subscribers = regionSubscribers.get(matchingRegions[regionIndex]);
                if (subscribers === undefined) {
                    continue;
                }
                forEachRegionSubscriber(subscribers, (formulaCellIndex) => {
                    seenDependents = pushAdaptiveUniqueDependent(dependents, seenDependents, formulaCellIndex);
                });
            }
            return Uint32Array.from(dependents);
        },
        collectSingleFormulaDependentForCell(sheetId, row, col) {
            const subscriptions = getColumnSubscriptions(sheetId, col);
            if (!rowCanIntersectSubscriptions(subscriptions, row)) {
                return -1;
            }
            const indexedDependent = collectSingleDependentFromPointImpact(subscriptions, row);
            if (indexedDependent !== undefined) {
                return indexedDependent;
            }
            if (subscriptions.dirty &&
                subscriptions.dirtyPointQueries < DIRTY_LINEAR_POINT_QUERY_LIMIT &&
                subscriptions.regionIds.size <= DIRTY_LINEAR_REGION_LIMIT) {
                subscriptions.dirtyPointQueries += 1;
                return collectSingleDependentForRegionIds(subscriptions.regionIds, row, nodeStore, regionSubscribers, subscriptions.orderedByRowStart);
            }
            return collectSingleDependentForRow(ensureIntervalTree(sheetId, col), row, regionSubscribers);
        },
        hasFormulaSubscriptionsForColumn(sheetId, col) {
            return (columnSubscriptions.get(columnKey(sheetId, col))?.regionIds.size ?? 0) > 0;
        },
        hasFormulaSubscriptionsOverlappingRange(sheetId, rowStart, rowEnd, colStart, colEnd) {
            if (rowEnd < rowStart || colEnd < colStart) {
                return false;
            }
            for (let col = colStart; col <= colEnd; col += 1) {
                const subscriptions = columnSubscriptions.get(columnKey(sheetId, col));
                if (subscriptions !== undefined &&
                    subscriptions.regionIds.size > 0 &&
                    subscriptions.maxRowEnd >= rowStart &&
                    subscriptions.minRowStart <= rowEnd) {
                    return true;
                }
            }
            return false;
        },
        hasFormulaSubscriptionsIntersectingRect(sheetId, rowStart, rowEnd, colStart, colEnd) {
            if (rowEnd < rowStart || colEnd < colStart) {
                return false;
            }
            for (let col = colStart; col <= colEnd; col += 1) {
                const subscriptions = columnSubscriptions.get(columnKey(sheetId, col));
                if (subscriptions !== undefined && rowCanIntersectSubscriptions(subscriptions, rowStart)) {
                    return true;
                }
                if (subscriptions !== undefined &&
                    rowEnd >= subscriptions.minRowStart &&
                    rowStart <= subscriptions.maxRowEnd &&
                    subscriptions.regionIds.size > 0) {
                    return true;
                }
            }
            return false;
        },
        getFormulaSubscriptionCount() {
            return formulaRegions.size;
        },
        hasFormulaSubscriptions() {
            return formulaRegions.size > 0;
        },
        reset() {
            regionSubscribers.clear();
            formulaRegions.clear();
            columnSubscriptions.clear();
            dirtyColumns.clear();
        },
    };
}
//# sourceMappingURL=region-graph.js.map