import { MAX_COLS } from '@bilig/protocol';
export function appendTrackedReverseEdge(registry, key, dependentCellIndex) {
    const existing = registry.get(key);
    if (existing) {
        existing.add(dependentCellIndex);
        return;
    }
    registry.set(key, new Set([dependentCellIndex]));
}
export function removeTrackedReverseEdge(registry, key, dependentCellIndex) {
    const existing = registry.get(key);
    if (!existing) {
        return;
    }
    existing.delete(dependentCellIndex);
    if (existing.size === 0) {
        registry.delete(key);
    }
}
export function collectTrackedDependents(registry, keys) {
    if (keys.length === 1) {
        const dependents = registry.get(keys[0]);
        return dependents === undefined || dependents.size === 0 ? [] : [...dependents];
    }
    const candidates = new Set();
    keys.forEach((key) => {
        registry.get(key)?.forEach((cellIndex) => {
            candidates.add(cellIndex);
        });
    });
    return [...candidates];
}
export function parseQualifiedDependencySheetName(dependency) {
    const delimiter = dependency.lastIndexOf('!');
    if (delimiter <= 0) {
        return undefined;
    }
    const qualifier = dependency.slice(0, delimiter);
    return qualifier.startsWith("'") && qualifier.endsWith("'") ? qualifier.slice(1, -1).replace(/''/g, "'") : qualifier;
}
export function hasQualifiedDependencies(compiled) {
    return compiled.deps.some((dependency) => dependency.includes('!'));
}
export function directRegionIdsForFormula(value) {
    if (!value.directAggregate && !value.directCriteria) {
        return [];
    }
    const regionIds = new Set();
    if (value.directCriteria) {
        if (value.directCriteria.aggregateRange) {
            regionIds.add(value.directCriteria.aggregateRange.regionId);
        }
        value.directCriteria.criteriaPairs.forEach((pair) => {
            regionIds.add(pair.range.regionId);
        });
    }
    return [...regionIds];
}
export function appendSheetRenameSourceTransform(formula, oldSheetName, newSheetName) {
    appendSheetRenameSourceTransformRecord(formula, { oldSheetName, newSheetName });
}
export function appendSheetRenameSourceTransformRecord(formula, transform) {
    const transforms = formula.sourceRenameTransforms;
    if (transforms) {
        const lastIndex = transforms.length - 1;
        const previous = transforms[lastIndex];
        const composed = previous === undefined ? null : composeConsecutiveSheetRenameTransform(previous, transform);
        if (composed === undefined) {
            transforms.pop();
            if (transforms.length === 1) {
                formula.sourceRenameTransform = transforms[0];
                formula.sourceRenameTransforms = undefined;
            }
            else if (transforms.length === 0) {
                formula.sourceRenameTransforms = undefined;
            }
            return;
        }
        if (composed !== null) {
            transforms[lastIndex] = composed;
            return;
        }
        transforms.push(transform);
        return;
    }
    const previous = formula.sourceRenameTransform;
    if (previous === undefined) {
        formula.sourceRenameTransform = transform;
        return;
    }
    const composed = composeConsecutiveSheetRenameTransform(previous, transform);
    if (composed === undefined) {
        formula.sourceRenameTransform = undefined;
        return;
    }
    if (composed !== null) {
        formula.sourceRenameTransform = composed;
        return;
    }
    formula.sourceRenameTransform = undefined;
    formula.sourceRenameTransforms = [previous, transform];
}
function composeConsecutiveSheetRenameTransform(previous, next) {
    if (previous.newSheetName !== next.oldSheetName) {
        return null;
    }
    if (previous.oldSheetName === next.newSheetName) {
        return undefined;
    }
    return { oldSheetName: previous.oldSheetName, newSheetName: next.newSheetName };
}
export function aggregateColumnDependencyKey(sheetId, col) {
    return sheetId * MAX_COLS + col;
}
const directAggregateColumnOwnerIndexes = new WeakMap();
function getOrCreateDirectAggregateColumnOwnerIndex(dependents) {
    let index = directAggregateColumnOwnerIndexes.get(dependents);
    if (index === undefined) {
        index = {
            intervalsByFormulaCellIndex: new Map(),
            intervalsByRowStart: [],
            prefixMaxRowEnd: [],
            unindexedFormulaCellIndices: new Set(),
            orderedByRowStart: true,
            lastRowStart: Number.NEGATIVE_INFINITY,
        };
        directAggregateColumnOwnerIndexes.set(dependents, index);
    }
    return index;
}
function appendDirectAggregateColumnOwnerInterval(dependents, formulaCellIndex, directAggregate) {
    appendDirectAggregateColumnOwnerRows(dependents, formulaCellIndex, directAggregate.rowStart, directAggregate.rowEnd);
}
function appendDirectAggregateColumnOwnerRows(dependents, formulaCellIndex, rowStart, rowEnd) {
    const index = getOrCreateDirectAggregateColumnOwnerIndex(dependents);
    const previous = index.intervalsByFormulaCellIndex.get(formulaCellIndex);
    if (previous !== undefined) {
        if (previous.rowStart === rowStart && previous.rowEnd === rowEnd) {
            return;
        }
        index.intervalsByFormulaCellIndex.delete(formulaCellIndex);
    }
    const interval = {
        formulaCellIndex,
        rowStart,
        rowEnd,
    };
    index.intervalsByFormulaCellIndex.set(formulaCellIndex, interval);
    index.intervalsByRowStart.push(interval);
    const previousPrefixMax = index.prefixMaxRowEnd[index.prefixMaxRowEnd.length - 1] ?? Number.NEGATIVE_INFINITY;
    index.prefixMaxRowEnd.push(Math.max(previousPrefixMax, interval.rowEnd));
    if (interval.rowStart < index.lastRowStart) {
        index.orderedByRowStart = false;
    }
    index.lastRowStart = interval.rowStart;
}
function appendFreshDirectAggregateColumnOwnerInterval(index, interval) {
    index.intervalsByFormulaCellIndex.set(interval.formulaCellIndex, interval);
    index.intervalsByRowStart.push(interval);
    const previousPrefixMax = index.prefixMaxRowEnd[index.prefixMaxRowEnd.length - 1] ?? Number.NEGATIVE_INFINITY;
    index.prefixMaxRowEnd.push(Math.max(previousPrefixMax, interval.rowEnd));
    if (interval.rowStart < index.lastRowStart) {
        index.orderedByRowStart = false;
    }
    index.lastRowStart = interval.rowStart;
}
function removeDirectAggregateColumnOwnerInterval(dependents, formulaCellIndex) {
    directAggregateColumnOwnerIndexes.get(dependents)?.intervalsByFormulaCellIndex.delete(formulaCellIndex);
}
function firstIntervalStartingAfter(intervals, row) {
    let low = 0;
    let high = intervals.length;
    while (low < high) {
        const mid = (low + high) >>> 1;
        if (intervals[mid].rowStart <= row) {
            low = mid + 1;
        }
        else {
            high = mid;
        }
    }
    return low;
}
export function visitIndexedDirectAggregateColumnDependentsForRow(dependents, row, visit) {
    const index = directAggregateColumnOwnerIndexes.get(dependents);
    if (index === undefined || !index.orderedByRowStart || index.unindexedFormulaCellIndices.size > 0) {
        return false;
    }
    const intervals = index.intervalsByRowStart;
    let cursor = firstIntervalStartingAfter(intervals, row) - 1;
    for (; cursor >= 0; cursor -= 1) {
        if ((index.prefixMaxRowEnd[cursor] ?? Number.NEGATIVE_INFINITY) < row) {
            break;
        }
        const interval = intervals[cursor];
        const current = index.intervalsByFormulaCellIndex.get(interval.formulaCellIndex);
        if (current !== interval || interval.rowEnd < row) {
            continue;
        }
        if (!visit(interval.formulaCellIndex)) {
            break;
        }
    }
    return true;
}
export function collectSingleIndexedDirectAggregateColumnDependentForRow(dependents, row) {
    const index = directAggregateColumnOwnerIndexes.get(dependents);
    if (index === undefined || !index.orderedByRowStart || index.unindexedFormulaCellIndices.size > 0) {
        return undefined;
    }
    const intervals = index.intervalsByRowStart;
    let singleDependent = -1;
    let cursor = firstIntervalStartingAfter(intervals, row) - 1;
    for (; cursor >= 0; cursor -= 1) {
        if ((index.prefixMaxRowEnd[cursor] ?? Number.NEGATIVE_INFINITY) < row) {
            break;
        }
        const interval = intervals[cursor];
        const current = index.intervalsByFormulaCellIndex.get(interval.formulaCellIndex);
        if (current !== interval || interval.rowEnd < row) {
            continue;
        }
        if (singleDependent !== -1 && singleDependent !== interval.formulaCellIndex) {
            return -2;
        }
        singleDependent = interval.formulaCellIndex;
    }
    return singleDependent;
}
export function collectIndexedDirectAggregateColumnDependentsForRow(dependents, row) {
    const collected = [];
    const usedIndex = visitIndexedDirectAggregateColumnDependentsForRow(dependents, row, (formulaCellIndex) => {
        collected.push(formulaCellIndex);
        return true;
    });
    return usedIndex ? collected.toReversed() : undefined;
}
function forEachDirectAggregateColumnDependencyKey(workbook, directAggregate, visit) {
    if (directAggregate === undefined) {
        return;
    }
    const sheet = workbook.getSheet(directAggregate.sheetName);
    if (!sheet) {
        return;
    }
    for (let col = directAggregate.col; col <= directAggregate.colEnd; col += 1) {
        visit(aggregateColumnDependencyKey(sheet.id, col));
    }
}
export function appendDirectAggregateColumnReverseEdges(registry, workbook, directAggregate, dependentCellIndex) {
    forEachDirectAggregateColumnDependencyKey(workbook, directAggregate, (key) => {
        appendTrackedReverseEdge(registry, key, dependentCellIndex);
        const dependents = registry.get(key);
        if (dependents !== undefined && directAggregate !== undefined) {
            appendDirectAggregateColumnOwnerInterval(dependents, dependentCellIndex, directAggregate);
        }
    });
}
export function canAppendFreshDirectAggregateColumnReverseEdgesForRun(workbook, cellIndices, members) {
    const first = members[0];
    if (first === undefined || cellIndices.length !== members.length || workbook.getSheet(first.aggregateSheetName) === undefined) {
        return false;
    }
    if (first.aggregateColStart > first.aggregateColEnd) {
        return false;
    }
    for (let index = 0; index < members.length; index += 1) {
        const member = members[index];
        if (cellIndices[index] === undefined ||
            member.aggregateSheetName !== first.aggregateSheetName ||
            member.aggregateColStart !== first.aggregateColStart ||
            member.aggregateColEnd !== first.aggregateColEnd) {
            return false;
        }
    }
    return true;
}
export function appendFreshDirectAggregateColumnReverseEdgesForRun(registry, workbook, cellIndices, members) {
    const first = members[0];
    const sheet = first === undefined ? undefined : workbook.getSheet(first.aggregateSheetName);
    if (first === undefined || sheet === undefined) {
        return;
    }
    const columnCount = first.aggregateColEnd - first.aggregateColStart + 1;
    const dependentSets = [];
    const ownerIndexes = [];
    dependentSets.length = columnCount;
    ownerIndexes.length = columnCount;
    for (let offset = 0; offset < columnCount; offset += 1) {
        const key = aggregateColumnDependencyKey(sheet.id, first.aggregateColStart + offset);
        let dependents = registry.get(key);
        if (dependents === undefined) {
            dependents = new Set();
            registry.set(key, dependents);
        }
        dependentSets[offset] = dependents;
        ownerIndexes[offset] = getOrCreateDirectAggregateColumnOwnerIndex(dependents);
    }
    for (let memberIndex = 0; memberIndex < members.length; memberIndex += 1) {
        const member = members[memberIndex];
        const formulaCellIndex = cellIndices[memberIndex];
        const interval = {
            formulaCellIndex,
            rowStart: member.aggregateRowStart,
            rowEnd: member.aggregateRowEnd,
        };
        for (let offset = 0; offset < columnCount; offset += 1) {
            dependentSets[offset].add(formulaCellIndex);
            appendFreshDirectAggregateColumnOwnerInterval(ownerIndexes[offset], interval);
        }
    }
}
export function removeDirectAggregateColumnReverseEdges(registry, workbook, directAggregate, dependentCellIndex) {
    forEachDirectAggregateColumnDependencyKey(workbook, directAggregate, (key) => {
        const dependents = registry.get(key);
        if (dependents !== undefined) {
            removeDirectAggregateColumnOwnerInterval(dependents, dependentCellIndex);
        }
        removeTrackedReverseEdge(registry, key, dependentCellIndex);
    });
}
export function appendUnindexedAggregateColumnReverseEdge(registry, key, dependentCellIndex) {
    appendTrackedReverseEdge(registry, key, dependentCellIndex);
    const dependents = registry.get(key);
    if (dependents !== undefined) {
        getOrCreateDirectAggregateColumnOwnerIndex(dependents).unindexedFormulaCellIndices.add(dependentCellIndex);
    }
}
export function appendDirectCriteriaAggregateColumnReverseEdge(registry, key, aggregateRange, dependentCellIndex) {
    appendTrackedReverseEdge(registry, key, dependentCellIndex);
    const dependents = registry.get(key);
    if (dependents !== undefined) {
        appendDirectAggregateColumnOwnerRows(dependents, dependentCellIndex, aggregateRange.rowStart, aggregateRange.rowEnd);
    }
}
export function removeUnindexedAggregateColumnReverseEdge(registry, key, dependentCellIndex) {
    const dependents = registry.get(key);
    if (dependents !== undefined) {
        directAggregateColumnOwnerIndexes.get(dependents)?.unindexedFormulaCellIndices.delete(dependentCellIndex);
    }
    removeTrackedReverseEdge(registry, key, dependentCellIndex);
}
export function removeDirectCriteriaAggregateColumnReverseEdge(registry, key, dependentCellIndex) {
    const dependents = registry.get(key);
    if (dependents !== undefined) {
        removeDirectAggregateColumnOwnerInterval(dependents, dependentCellIndex);
    }
    removeTrackedReverseEdge(registry, key, dependentCellIndex);
}
export function directCriteriaAggregateColumn(value) {
    const aggregateRange = value?.aggregateRange;
    return aggregateRange
        ? {
            sheetName: aggregateRange.sheetName,
            rowStart: aggregateRange.rowStart,
            rowEnd: aggregateRange.rowEnd,
            col: aggregateRange.col,
        }
        : undefined;
}
export function formulaColumnCountKey(sheetId, col) {
    return sheetId * MAX_COLS + col;
}
export function directLookupColumnInfo(directLookup) {
    switch (directLookup.kind) {
        case 'exact':
            return {
                sheetName: directLookup.prepared.sheetName,
                col: directLookup.prepared.col,
                isExact: true,
            };
        case 'exact-uniform-numeric':
            return {
                sheetName: directLookup.sheetName,
                col: directLookup.col,
                isExact: true,
            };
        case 'approximate':
            return {
                sheetName: directLookup.prepared.sheetName,
                col: directLookup.prepared.col,
                isExact: false,
            };
        case 'approximate-uniform-numeric':
            return {
                sheetName: directLookup.sheetName,
                col: directLookup.col,
                isExact: false,
            };
    }
}
//# sourceMappingURL=formula-binding-dependency-helpers.js.map