import { FormulaMode, ErrorCode } from '@bilig/protocol';
import { entityPayload, isRangeEntity, makeCellEntity } from '../../entity-ids.js';
import { tableDependencyKey } from '../../engine-metadata-utils.js';
import { errorValue } from '../../engine-value-utils.js';
import { addEngineCounter } from '../../perf/engine-counters.js';
import { normalizeDefinedName } from '../../workbook-store.js';
import { canRewriteCompiledPreservingBindings, canRewriteCompiledPreservingDirectAggregate, canRewriteCompiledPreservingDirectScalar, directScalarDependencyCellsEqual, directAggregateStructureEqual, directCriteriaStructureEqual, directLookupStructureEqual, hasInPlaceDependencyRebindShape, stringArrayEqual, uint32ArrayEqual, } from './formula-binding-shape-helpers.js';
import { buildDirectScalarDescriptor } from './formula-binding-direct-scalar.js';
import { aggregateColumnDependencyKey, appendDirectAggregateColumnReverseEdges, appendTrackedReverseEdge, collectTrackedDependents, directCriteriaAggregateColumn, directRegionIdsForFormula, hasQualifiedDependencies, removeDirectAggregateColumnReverseEdges, removeDirectCriteriaAggregateColumnReverseEdge, } from './formula-binding-dependency-helpers.js';
import { buildDirectAggregateDescriptor, rewriteDirectAggregateDescriptorForStructuralTransform, } from './formula-binding-direct-descriptors.js';
import { appendFormulaBindingReverseEdge, appendKnownUniqueFormulaBindingReverseEdge, getFormulaBindingReverseEdgeSlice, removeFormulaBindingReverseEdge, setFormulaBindingReverseEdgeSlice, } from './formula-binding-reverse-edges.js';
import { createFormulaBindingSheetIndex } from './formula-binding-sheet-index.js';
import { createFormulaBindingMemberCounts } from './formula-binding-member-counts.js';
import { createFormulaBindingDependencyMaterializer } from './formula-binding-dependency-materializer.js';
import { createPendingInitialFormulaCellTracker } from './formula-binding-pending-formula-cells.js';
import { compileFormulaBindingForCell } from './formula-binding-compile.js';
import { prepareFormulaBindingFromCompiled } from './formula-binding-prepare.js';
import { createFormulaBindingInstanceTracker } from './formula-binding-instance-tracker.js';
import { clearFormulaBindingNow } from './formula-binding-clear.js';
import { installFreshFormulaBindingNow } from './formula-binding-install.js';
import { createFormulaBindingSheetRenameHandler } from './formula-binding-sheet-rename.js';
import { createFormulaBindingRebinds } from './formula-binding-rebind.js';
import { canRetainUnmanagedCompiledPlan, makeUnmanagedCompiledPlan } from './formula-binding-plan-helpers.js';
import { normalizeFormulaBindingLookupCompileMode } from './formula-binding-lookup-mode.js';
import { primeFormulaBindingLookupCandidates } from './formula-binding-lookup-primer.js';
import { directAggregateContainsFormulaOwnerCell } from './formula-binding-direct-aggregate-owner.js';
import { ensureFormulaBindingDependencyBuildCapacity } from './formula-binding-dependency-build-capacity.js';
import { createFormulaBindingRangeDependencyUpdater } from './formula-binding-range-dependencies.js';
import { clearFormulaRuntimeFlags } from './formula-binding-cell-flags.js';
import { formulaBindingEffect } from './formula-binding-effect.js';
import { rebuildAllFormulaBindingsNow } from './formula-binding-rebuild.js';
import { createFormulaBindingFamilyIndexController } from './formula-binding-family-index-controller.js';
import { applyDirectScalarReplacementRuntimePlanFields, applyFormulaRuntimePlanFields } from './formula-binding-runtime-update.js';
import { rebuildDeferredFormulaFamilyIndex } from './formula-family-index-rebuild.js';
import { bindFreshDirectAggregateFormulaRun } from './formula-binding-fresh-direct-aggregate-run.js';
import { bindFreshDirectScalarFormulaRun } from './formula-binding-fresh-direct-scalar-run.js';
import { createFormulaBindingInstanceTableRebuildController } from './formula-binding-instance-table-rebuild.js';
import { tryRewriteSimpleDirectScalarFormulaSourcePreservingBinding } from './formula-binding-direct-scalar-rewrite.js';
import { updateFormulaBindingVolatileIndex } from './formula-binding-volatile-index.js';
import { refreshFormulaBindingTrackedMetadata } from './formula-binding-tracked-metadata.js';
export { formulaBindingServiceTestHooks } from './formula-binding-service-test-hooks.js';
export function createEngineFormulaBindingService(args) {
    const resolvedCompiledCache = new Map();
    const formulaMemberCounts = createFormulaBindingMemberCounts();
    const formulaSheetIndex = createFormulaBindingSheetIndex();
    const formulaFamilyShapeKeyCache = new Map();
    const { rebuildFormulaInstancesNow, recordFormulaInstanceNow, registerFormulaFamilyNow: registerFormulaFamilyInStoreNow, } = createFormulaBindingInstanceTracker({
        serviceArgs: args,
        formulaFamilyShapeKeyCache,
    });
    const formulaInstanceTableRebuild = createFormulaBindingInstanceTableRebuildController({
        formulaInstances: args.formulaInstances,
        rebuildFormulaInstancesNow,
        recordFormulaInstanceNow,
    });
    const formulaFamilyIndex = createFormulaBindingFamilyIndexController({
        formulaFamilies: args.formulaFamilies,
        formulaFamilyShapeKeyCache,
        registerFormulaFamilyInStoreNow,
        countFormulaSheetMembersNow: (sheetId) => formulaMemberCounts.countSheetMembers(sheetId),
        rebuildFormulaFamilyIndexNow: () => rebuildDeferredFormulaFamilyIndex({
            state: args.state,
            store: args.formulaFamilies,
            shapeKeyCache: formulaFamilyShapeKeyCache,
        }),
    });
    const { registerFormulaFamilyNow } = formulaFamilyIndex;
    const clearFormulaBookkeepingNow = () => {
        resolvedCompiledCache.clear();
        formulaMemberCounts.clear();
        formulaSheetIndex.clear();
        formulaFamilyIndex.clearNow();
        formulaInstanceTableRebuild.clearRebuildNow();
    };
    const updateVolatileFormulaIndex = (cellIndex, formula) => updateFormulaBindingVolatileIndex(args.volatileFormulaCells, cellIndex, formula);
    const trackFormulaSheetIndexes = (cellIndex, ownerSheetName, compiled) => {
        formulaSheetIndex.trackFormula(cellIndex, ownerSheetName, compiled);
    };
    const trackFormulaSheetOwnerRun = (ownerSheetName, cellIndices) => {
        formulaSheetIndex.trackFormulaOwnerRun(ownerSheetName, cellIndices);
    };
    const untrackFormulaSheetIndexes = (cellIndex, ownerSheetName, compiled) => {
        formulaSheetIndex.untrackFormula(cellIndex, ownerSheetName, compiled);
    };
    const ensureDependencyBuildCapacity = (cellCapacity, dependencyCapacity, symbolicRefCapacity = 0, symbolicRangeCapacity = 0) => ensureFormulaBindingDependencyBuildCapacity(args, cellCapacity, dependencyCapacity, symbolicRefCapacity, symbolicRangeCapacity);
    const pendingInitialFormulaCells = createPendingInitialFormulaCellTracker({
        getCellCapacity: () => args.state.workbook.cellStore.capacity,
        getSheetId: (cellIndex) => args.state.workbook.cellStore.sheetIds[cellIndex],
        getCol: (cellIndex) => {
            const sheetId = args.state.workbook.cellStore.sheetIds[cellIndex];
            if (sheetId !== undefined && args.state.workbook.getSheetById(sheetId)?.structureVersion === 1) {
                return args.state.workbook.cellStore.cols[cellIndex];
            }
            return args.state.workbook.getCellPosition(cellIndex)?.col ?? args.state.workbook.cellStore.cols[cellIndex];
        },
        isBoundFormulaCell: (cellIndex) => (args.state.workbook.cellStore.formulaIds[cellIndex] ?? 0) !== 0,
        hasBoundColumnMembers: (sheetId, col) => formulaMemberCounts.hasColumnMembers(sheetId, col),
    });
    const dependencyMaterializer = createFormulaBindingDependencyMaterializer({
        serviceArgs: args,
        hasFormulaColumnMembers: pendingInitialFormulaCells.hasColumnMembers,
        isFormulaCell: pendingInitialFormulaCells.isFormulaCell,
        ensureDependencyBuildCapacity,
    });
    const setReverseEdgeSlice = (entityId, slice) => {
        setFormulaBindingReverseEdgeSlice(args.reverseState, entityId, slice);
    };
    const getReverseEdgeSlice = (entityId) => getFormulaBindingReverseEdgeSlice(args.reverseState, entityId);
    const appendReverseEdge = (entityId, dependentEntityId) => {
        appendFormulaBindingReverseEdge(args.reverseState, args.edgeArena, entityId, dependentEntityId);
    };
    const appendKnownUniqueReverseEdge = (entityId, dependentEntityId) => {
        appendKnownUniqueFormulaBindingReverseEdge(args.reverseState, args.edgeArena, entityId, dependentEntityId);
    };
    const removeReverseEdge = (entityId, dependentEntityId) => {
        removeFormulaBindingReverseEdge(args.reverseState, args.edgeArena, entityId, dependentEntityId);
    };
    const { refreshRangeDependenciesNow, retargetRangeDependenciesNow } = createFormulaBindingRangeDependencyUpdater(args);
    const rangeDependenciesHaveNoFormulaMembers = (rangeDependencies) => rangeDependencies.every((rangeIndex) => args.state.ranges.getFormulaMembersView(rangeIndex).length === 0);
    const appendDefinedNameReverseEdge = (name, dependentCellIndex) => {
        appendTrackedReverseEdge(args.reverseState.reverseDefinedNameEdges, normalizeDefinedName(name), dependentCellIndex);
    };
    const pruneTrackedDependencyCell = (cellIndex, ownerCellIndex) => {
        if (cellIndex === ownerCellIndex) {
            return;
        }
        if (getReverseEdgeSlice(makeCellEntity(cellIndex))) {
            return;
        }
        if (((args.state.workbook.cellStore.flags[cellIndex] ?? 0) & 256 /* CellFlags.AuthoredBlank */) !== 0) {
            return;
        }
        args.state.workbook.pruneCellIfEmpty(cellIndex);
    };
    const pruneOrphanedDependencyCells = (cellIndices) => {
        cellIndices.forEach((cellIndex) => {
            if (getReverseEdgeSlice(makeCellEntity(cellIndex))) {
                return;
            }
            if (((args.state.workbook.cellStore.flags[cellIndex] ?? 0) & 256 /* CellFlags.AuthoredBlank */) !== 0) {
                return;
            }
            args.state.workbook.pruneCellIfEmpty(cellIndex);
        });
    };
    const updateFormulaDependenciesInPlaceNow = (cellIndex, existing, prepared, ownerSheetName, source) => {
        const formulaEntity = makeCellEntity(cellIndex);
        const previousDependencies = args.edgeArena.readView(existing.dependencyEntities);
        const nextDependencies = prepared.dependencies.dependencyEntities;
        const dependencyEntitiesChanged = !uint32ArrayEqual(previousDependencies, nextDependencies);
        if (dependencyEntitiesChanged) {
            const nextDependencySet = new Set(nextDependencies);
            const previousDependencySet = new Set(previousDependencies);
            previousDependencies.forEach((dependencyEntity) => {
                if (nextDependencySet.has(dependencyEntity)) {
                    return;
                }
                removeReverseEdge(dependencyEntity, formulaEntity);
                if (!isRangeEntity(dependencyEntity)) {
                    pruneTrackedDependencyCell(entityPayload(dependencyEntity), cellIndex);
                }
            });
            nextDependencies.forEach((dependencyEntity) => {
                if (previousDependencySet.has(dependencyEntity)) {
                    return;
                }
                appendReverseEdge(dependencyEntity, formulaEntity);
            });
        }
        const plan = canRetainUnmanagedCompiledPlan(existing.planId, prepared.plan.compiled, prepared.directScalar)
            ? prepared.plan
            : args.compiledPlans.replace(existing.planId, source, prepared.plan.compiled, prepared.templateId);
        args.compiledPlans.release(prepared.plan.id);
        untrackFormulaSheetIndexes(cellIndex, ownerSheetName, existing.compiled);
        applyFormulaRuntimePlanFields(existing, {
            source,
            plan,
            templateId: prepared.templateId,
            runtimeProgram: prepared.runtimeProgram,
            programLength: prepared.runtimeProgram.length,
            inlineScalarFastPlanKind: prepared.inlineScalarFastPlanKind,
            inlineScalarArithmeticDeltaCoefficients: prepared.inlineScalarArithmeticDeltaCoefficients,
            inlineScalarFastPlanStringIds: prepared.inlineScalarFastPlanStringIds,
            inlineScalarPlanCellIndices: prepared.inlineScalarPlanCellIndices,
        });
        existing.dependencyIndices = prepared.dependencies.dependencyIndices;
        if (dependencyEntitiesChanged) {
            existing.dependencyEntities = args.edgeArena.replace(existing.dependencyEntities, nextDependencies);
        }
        existing.rangeDependencies = prepared.dependencies.rangeDependencies;
        existing.graphRangeDependencies = prepared.dependencies.graphRangeDependencies;
        existing.directLookup = undefined;
        existing.directAggregate = undefined;
        existing.directScalar = prepared.directScalar;
        existing.directCriteria = undefined;
        refreshFormulaBindingTrackedMetadata(args, cellIndex, existing);
        if (prepared.compiled.mode === FormulaMode.WasmFastPath && prepared.runtimeProgram.length > 0) {
            args.scheduleWasmProgramSync();
        }
        recordFormulaInstanceNow(cellIndex, source, prepared.templateId);
        registerFormulaFamilyNow(cellIndex, existing);
        trackFormulaSheetIndexes(cellIndex, ownerSheetName, existing.compiled);
        args.regionGraph.replaceFormulaSubscriptions(cellIndex, directRegionIdsForFormula(existing));
        primeLookupCandidatesNow(ownerSheetName, undefined, prepared.indexedExactLookupCandidates, prepared.directApproximateLookupCandidates);
    };
    const rewriteFormulaCompiledPreservingBindingNow = (cellIndex, source, compiled, templateId, ownerPosition) => {
        const existing = args.state.formulas.get(cellIndex);
        if (!existing) {
            return false;
        }
        const ownerSheetName = ownerPosition?.sheetName ?? args.state.workbook.getSheetNameById(args.state.workbook.cellStore.sheetIds[cellIndex]);
        if (!ownerSheetName) {
            return false;
        }
        const ownerSheetId = ownerPosition?.sheetName
            ? args.state.workbook.getSheet(ownerPosition.sheetName)?.id
            : args.state.workbook.cellStore.sheetIds[cellIndex];
        let nextDirectAggregate;
        let nextDirectScalar;
        if (canRewriteCompiledPreservingBindings(existing, compiled) && rangeDependenciesHaveNoFormulaMembers(existing.rangeDependencies)) {
            nextDirectAggregate = undefined;
            nextDirectScalar =
                existing.directScalar === undefined
                    ? undefined
                    : buildDirectScalarDescriptor({
                        compiled: compiled,
                        ownerSheetName,
                        ownerSheetId,
                        workbook: args.state.workbook,
                        ensureCellTracked: args.ensureCellTracked,
                        ensureCellTrackedByCoords: args.ensureCellTrackedByCoords,
                    });
            if (existing.directScalar !== undefined && !nextDirectScalar) {
                return false;
            }
        }
        else if (canRewriteCompiledPreservingDirectAggregate(existing, compiled)) {
            nextDirectAggregate = buildDirectAggregateDescriptor({
                compiled: compiled,
                ownerSheetName,
                workbook: args.state.workbook,
                regionGraph: args.regionGraph,
            });
            if (!nextDirectAggregate) {
                return false;
            }
        }
        else if (canRewriteCompiledPreservingDirectScalar(existing, compiled)) {
            const replacementDirectScalar = buildDirectScalarDescriptor({
                compiled: compiled,
                ownerSheetName,
                ownerSheetId,
                workbook: args.state.workbook,
                ensureCellTracked: args.ensureCellTracked,
                ensureCellTrackedByCoords: args.ensureCellTrackedByCoords,
            });
            if (replacementDirectScalar && directScalarDependencyCellsEqual(existing.directScalar, replacementDirectScalar)) {
                const nextTemplateId = templateId ?? existing.templateId;
                const plan = canRetainUnmanagedCompiledPlan(existing.planId, compiled, replacementDirectScalar)
                    ? makeUnmanagedCompiledPlan(source, compiled, nextTemplateId)
                    : args.compiledPlans.replace(existing.planId, source, compiled, nextTemplateId);
                applyDirectScalarReplacementRuntimePlanFields(existing, {
                    source,
                    plan,
                    templateId: nextTemplateId,
                });
                existing.directScalar = replacementDirectScalar;
                refreshFormulaBindingTrackedMetadata(args, cellIndex, existing);
                if (compiled.mode === FormulaMode.WasmFastPath && existing.runtimeProgram.length > 0) {
                    args.scheduleWasmProgramSync();
                }
                recordFormulaInstanceNow(cellIndex, source, nextTemplateId, ownerPosition);
                registerFormulaFamilyNow(cellIndex, existing, ownerPosition);
                return true;
            }
            updateFormulaDependenciesInPlaceNow(cellIndex, existing, prepareFormulaBindingFromCompiledNow(cellIndex, ownerSheetName, source, compiled, templateId), ownerSheetName, source);
            return true;
        }
        else {
            return false;
        }
        const nextTemplateId = templateId ?? existing.templateId;
        const plan = canRetainUnmanagedCompiledPlan(existing.planId, compiled, nextDirectScalar)
            ? makeUnmanagedCompiledPlan(source, compiled, nextTemplateId)
            : args.compiledPlans.replace(existing.planId, source, compiled, nextTemplateId);
        const previousDirectAggregate = existing.directAggregate;
        const previousDirectCriteriaAggregate = directCriteriaAggregateColumn(existing.directCriteria);
        const shouldRefreshSheetIndexes = hasQualifiedDependencies(existing.compiled) || hasQualifiedDependencies(compiled);
        if (shouldRefreshSheetIndexes) {
            untrackFormulaSheetIndexes(cellIndex, ownerSheetName, existing.compiled);
        }
        const hadDirectRegions = existing.directAggregate !== undefined || existing.directCriteria !== undefined;
        applyFormulaRuntimePlanFields(existing, {
            source,
            plan,
            templateId: nextTemplateId,
            programLength: compiled.program.length,
        });
        existing.directAggregate = nextDirectAggregate;
        existing.directScalar = nextDirectScalar;
        existing.directCriteria = undefined;
        refreshFormulaBindingTrackedMetadata(args, cellIndex, existing);
        removeDirectAggregateColumnReverseEdges(args.reverseState.reverseAggregateColumnEdges, args.state.workbook, previousDirectAggregate, cellIndex);
        appendDirectAggregateColumnReverseEdges(args.reverseState.reverseAggregateColumnEdges, args.state.workbook, existing.directAggregate, cellIndex);
        if (previousDirectCriteriaAggregate) {
            const previousCriteriaSheet = args.state.workbook.getSheet(previousDirectCriteriaAggregate.sheetName);
            if (previousCriteriaSheet) {
                removeDirectCriteriaAggregateColumnReverseEdge(args.reverseState.reverseAggregateColumnEdges, aggregateColumnDependencyKey(previousCriteriaSheet.id, previousDirectCriteriaAggregate.col), cellIndex);
            }
        }
        recordFormulaInstanceNow(cellIndex, source, nextTemplateId, ownerPosition);
        registerFormulaFamilyNow(cellIndex, existing);
        if (shouldRefreshSheetIndexes) {
            trackFormulaSheetIndexes(cellIndex, ownerSheetName, existing.compiled);
        }
        if (hadDirectRegions || existing.directAggregate !== undefined || existing.directCriteria !== undefined) {
            args.regionGraph.replaceFormulaSubscriptions(cellIndex, directRegionIdsForFormula(existing));
        }
        return true;
    };
    const rewriteFormulaMetadataPreservingRuntimeNow = (cellIndex, source, compiled, templateId, ownerPosition) => {
        const existing = args.state.formulas.get(cellIndex);
        if (!existing ||
            existing.directLookup !== undefined ||
            existing.directAggregate !== undefined ||
            existing.directCriteria !== undefined ||
            !rangeDependenciesHaveNoFormulaMembers(existing.rangeDependencies) ||
            !canRewriteCompiledPreservingBindings(existing, compiled)) {
            return false;
        }
        const nextTemplateId = templateId ?? existing.templateId;
        const plan = canRetainUnmanagedCompiledPlan(existing.planId, compiled, existing.directScalar)
            ? makeUnmanagedCompiledPlan(source, compiled, nextTemplateId)
            : args.compiledPlans.replace(existing.planId, source, compiled, nextTemplateId);
        const ownerSheetName = ownerPosition?.sheetName ?? args.state.workbook.getSheetNameById(args.state.workbook.cellStore.sheetIds[cellIndex]);
        const shouldRefreshSheetIndexes = ownerSheetName !== undefined && (hasQualifiedDependencies(existing.compiled) || hasQualifiedDependencies(compiled));
        if (shouldRefreshSheetIndexes) {
            untrackFormulaSheetIndexes(cellIndex, ownerSheetName, existing.compiled);
        }
        applyFormulaRuntimePlanFields(existing, {
            source,
            plan,
            templateId: nextTemplateId,
            programLength: existing.runtimeProgram.length,
        });
        updateVolatileFormulaIndex(cellIndex, existing);
        formulaInstanceTableRebuild.recordIfMissingOrRebuildingNow(cellIndex, source, nextTemplateId, ownerPosition);
        if (shouldRefreshSheetIndexes) {
            trackFormulaSheetIndexes(cellIndex, ownerSheetName, existing.compiled);
        }
        return true;
    };
    const retargetDirectAggregateFormulaForStructuralTransformNow = (cellIndex, ownerSheetName, targetSheetName, transform, preservesValue) => {
        const existing = args.state.formulas.get(cellIndex);
        if (!existing?.directAggregate) {
            return false;
        }
        const previousDirectAggregate = existing.directAggregate;
        const nextDirectAggregate = rewriteDirectAggregateDescriptorForStructuralTransform({
            descriptor: previousDirectAggregate,
            targetSheetName,
            transform,
            regionGraph: args.regionGraph,
        });
        if (!nextDirectAggregate) {
            return false;
        }
        existing.directAggregate = nextDirectAggregate;
        existing.structuralSourceTransform = {
            ownerSheetName,
            targetSheetName,
            transform,
            preservesValue,
        };
        args.regionGraph.replaceSingleFormulaSubscription(cellIndex, previousDirectAggregate.regionId, nextDirectAggregate.regionId);
        return true;
    };
    const retargetDirectAggregateFormulasForStructuralTransformNow = (inputs, targetSheetName, transform) => {
        if (inputs.length === 0) {
            return [];
        }
        const retargetedCellIndices = [];
        const replacements = [];
        for (let index = 0; index < inputs.length; index += 1) {
            const { cellIndex, ownerSheetName, preservesValue } = inputs[index];
            const existing = args.state.formulas.get(cellIndex);
            if (!existing?.directAggregate) {
                continue;
            }
            const previousDirectAggregate = existing.directAggregate;
            const nextDirectAggregate = rewriteDirectAggregateDescriptorForStructuralTransform({
                descriptor: previousDirectAggregate,
                targetSheetName,
                transform,
                regionGraph: args.regionGraph,
            });
            if (!nextDirectAggregate) {
                continue;
            }
            existing.directAggregate = nextDirectAggregate;
            existing.structuralSourceTransform = {
                ownerSheetName,
                targetSheetName,
                transform,
                preservesValue,
            };
            retargetedCellIndices.push(cellIndex);
            replacements.push({
                formulaCellIndex: cellIndex,
                previousRegionId: previousDirectAggregate.regionId,
                nextRegionId: nextDirectAggregate.regionId,
            });
        }
        args.regionGraph.replaceSingleFormulaSubscriptions(replacements);
        return retargetedCellIndices;
    };
    const isCellIndexMappedNow = (cellIndex) => {
        const sheetId = args.state.workbook.cellStore.sheetIds[cellIndex];
        const position = args.state.workbook.getCellPosition(cellIndex);
        if (sheetId === undefined || !position) {
            return false;
        }
        const sheet = args.state.workbook.getSheetById(sheetId);
        return sheet?.grid.get(position.row, position.col) === cellIndex;
    };
    const compileFormulaForCell = (cellIndex, currentSheetName, source) => compileFormulaBindingForCell({
        serviceArgs: args,
        cellIndex,
        currentSheetName,
        source,
        resolvedCompiledCache,
        normalizeLookupCompileMode: normalizeFormulaBindingLookupCompileMode,
    });
    const clearFormulaNow = (cellIndex) => {
        if (!formulaFamilyIndex.isReadyNow()) {
            formulaFamilyIndex.deferRebuildNow();
        }
        return clearFormulaBindingNow({
            serviceArgs: args,
            formulaMemberCounts,
            untrackFormulaSheetIndexes,
            removeReverseEdge,
            setReverseEdgeSlice,
            pruneTrackedDependencyCell,
            updateVolatileFormulaIndex,
            cellIndex,
        });
    };
    const bindPreparedFormulaPreparedNow = (cellIndex, ownerSheetName, source, prepared, options = {}) => {
        if (options.assumeFreshFormula === true && (args.state.workbook.cellStore.formulaIds[cellIndex] ?? 0) === 0) {
            installFreshFormulaNow(cellIndex, ownerSheetName, source, prepared, options);
            return true;
        }
        const existing = args.state.formulas.get(cellIndex);
        const topologyChanged = existing === undefined ||
            !uint32ArrayEqual(args.edgeArena.readView(existing.dependencyEntities), prepared.dependencies.dependencyEntities) ||
            !uint32ArrayEqual(existing.rangeDependencies, prepared.dependencies.rangeDependencies) ||
            !stringArrayEqual(existing.compiled.symbolicNames, prepared.compiled.symbolicNames) ||
            !stringArrayEqual(existing.compiled.symbolicTables, prepared.compiled.symbolicTables) ||
            !stringArrayEqual(existing.compiled.symbolicSpills, prepared.compiled.symbolicSpills) ||
            !directLookupStructureEqual(existing.directLookup, prepared.directLookup) ||
            !directAggregateStructureEqual(existing.directAggregate, prepared.directAggregate) ||
            !directCriteriaStructureEqual(existing.directCriteria, prepared.directCriteria);
        if (existing && !topologyChanged) {
            args.compiledPlans.release(existing.planId);
            applyFormulaRuntimePlanFields(existing, {
                source,
                plan: prepared.plan,
                templateId: prepared.templateId,
                runtimeProgram: prepared.runtimeProgram,
                programLength: prepared.runtimeProgram.length,
                inlineScalarFastPlanKind: prepared.inlineScalarFastPlanKind,
                inlineScalarArithmeticDeltaCoefficients: prepared.inlineScalarArithmeticDeltaCoefficients,
                inlineScalarFastPlanStringIds: prepared.inlineScalarFastPlanStringIds,
                inlineScalarPlanCellIndices: prepared.inlineScalarPlanCellIndices,
            });
            existing.dependencyIndices = prepared.dependencies.dependencyIndices;
            existing.directLookup = prepared.directLookup;
            existing.directAggregate = prepared.directAggregate;
            existing.directScalar = prepared.directScalar;
            existing.directCriteria = prepared.directCriteria;
            if (options.preserveCachedValueOnFullRecalc === true) {
                existing.preserveCachedValueOnFullRecalc = true;
            }
            else {
                delete existing.preserveCachedValueOnFullRecalc;
            }
            refreshFormulaBindingTrackedMetadata(args, cellIndex, existing);
            if (prepared.compiled.mode === FormulaMode.WasmFastPath && prepared.runtimeProgram.length > 0) {
                args.scheduleWasmProgramSync();
            }
            if (options.deferFormulaInstanceRegistration !== true) {
                recordFormulaInstanceNow(cellIndex, source, prepared.templateId);
            }
            if (options.deferFamilyRegistration !== true) {
                registerFormulaFamilyNow(cellIndex, existing);
            }
            primeLookupCandidatesNow(ownerSheetName, prepared.directLookup, prepared.indexedExactLookupCandidates, prepared.directApproximateLookupCandidates);
            return false;
        }
        if (existing && hasInPlaceDependencyRebindShape(existing, prepared)) {
            updateFormulaDependenciesInPlaceNow(cellIndex, existing, prepared, ownerSheetName, source);
            return topologyChanged;
        }
        if (existing) {
            clearFormulaNow(cellIndex);
        }
        installFreshFormulaNow(cellIndex, ownerSheetName, source, prepared, options);
        return topologyChanged;
    };
    const bindFormulaNow = (cellIndex, ownerSheetName, source) => {
        if (args.state.counters) {
            addEngineCounter(args.state.counters, 'formulasBound');
        }
        return bindPreparedFormulaPreparedNow(cellIndex, ownerSheetName, source, prepareFormulaBindingNow(cellIndex, ownerSheetName, source));
    };
    const bindPreparedFormulaNow = (cellIndex, ownerSheetName, source, compiled, templateId, options = {}) => bindPreparedFormulaPreparedNow(cellIndex, ownerSheetName, source, prepareFormulaBindingFromCompiledNow(cellIndex, ownerSheetName, source, compiled, templateId, options.ownerPosition, options.assumeFreshDirectAggregateLiteralInputs, options.resolveWorkbookDateSystem), options);
    const rewriteFormulaSourcePreservingBindingNow = (cellIndex, ownerSheetName, source) => {
        if (tryRewriteSimpleDirectScalarFormulaSourcePreservingBinding(args.state.formulas.get(cellIndex), args.state.workbook.metadata.definedNames.size, source, (compiled) => rewriteFormulaCompiledPreservingBindingNow(cellIndex, source, compiled))) {
            return true;
        }
        const { compiled, templateResolution } = compileFormulaForCell(cellIndex, ownerSheetName, source);
        return rewriteFormulaCompiledPreservingBindingNow(cellIndex, source, compiled, templateResolution.templateId);
    };
    const bindInitialFormulaNow = (cellIndex, ownerSheetName, source) => {
        if (args.state.counters) {
            addEngineCounter(args.state.counters, 'formulasBound');
        }
        installFreshFormulaNow(cellIndex, ownerSheetName, source, prepareFormulaBindingNow(cellIndex, ownerSheetName, source));
    };
    const installFreshFormulaNow = (cellIndex, ownerSheetName, source, prepared, options = {}) => {
        installFreshFormulaBindingNow({
            serviceArgs: args,
            formulaMemberCounts,
            appendReverseEdge,
            appendKnownUniqueReverseEdge,
            appendDefinedNameReverseEdge,
            trackFormulaSheetIndexes,
            updateVolatileFormulaIndex,
            recordFormulaInstanceNow,
            registerFormulaFamilyNow,
            primeLookupCandidatesNow,
            cellIndex,
            ownerSheetName,
            source,
            prepared,
            options,
        });
    };
    const primeLookupCandidatesNow = (ownerSheetName, directLookup, indexedExactLookupCandidates, directApproximateLookupCandidates) => {
        primeFormulaBindingLookupCandidates({
            serviceArgs: args,
            ownerSheetName,
            directLookup,
            indexedExactLookupCandidates,
            directApproximateLookupCandidates,
        });
    };
    const prepareFormulaBindingFromCompiledNow = (cellIndex, ownerSheetName, source, compiledInput, templateId, ownerPosition, assumeFreshDirectAggregateLiteralInputs, resolveWorkbookDateSystem) => {
        return prepareFormulaBindingFromCompiled({
            serviceArgs: args,
            cellIndex,
            ownerSheetName,
            source,
            compiledInput,
            templateId,
            ...(ownerPosition !== undefined ? { ownerPosition } : {}),
            ...(assumeFreshDirectAggregateLiteralInputs === true ? { assumeFreshDirectAggregateLiteralInputs } : {}),
            ...(resolveWorkbookDateSystem !== undefined ? { resolveWorkbookDateSystem } : {}),
            normalizeLookupCompileMode: normalizeFormulaBindingLookupCompileMode,
            dependencyMaterializer,
            ensureDependencyBuildCapacity,
            directAggregateContainsOwnerCell: (directAggregate, ownerCellIndex) => directAggregateContainsFormulaOwnerCell(args, directAggregate, ownerCellIndex, ownerPosition),
            makeUnmanagedCompiledPlan,
        });
    };
    const prepareFormulaBindingNow = (cellIndex, ownerSheetName, source) => {
        const { compiled, templateResolution } = compileFormulaForCell(cellIndex, ownerSheetName, source);
        return prepareFormulaBindingFromCompiledNow(cellIndex, ownerSheetName, source, compiled, templateResolution.templateId);
    };
    const invalidateFormulaNow = (cellIndex) => {
        clearFormulaNow(cellIndex);
        args.state.workbook.cellStore.setValue(cellIndex, errorValue(ErrorCode.Value));
        clearFormulaRuntimeFlags(args.state.workbook.cellStore, cellIndex);
    };
    const { rebindFormulaCellsNow, rebindTrackedDependentsNow, rebindFormulasForSheetNow } = createFormulaBindingRebinds({
        serviceArgs: args,
        bindFormulaNow,
    });
    const { deferCellFormulasForSheetRenameNow, rewriteCellFormulasForSheetRenameNow } = createFormulaBindingSheetRenameHandler({
        serviceArgs: args,
        formulaSheetIndex,
        rangeDependenciesHaveNoFormulaMembers,
        untrackFormulaSheetIndexes,
        trackFormulaSheetIndexes,
        canRetainUnmanagedCompiledPlan,
        makeUnmanagedCompiledPlan,
        rewriteFormulaMetadataPreservingRuntimeNow,
        bindPreparedFormulaNow,
    });
    return {
        bindFormula(cellIndex, ownerSheetName, source) {
            return formulaBindingEffect('Failed to bind formula', () => bindFormulaNow(cellIndex, ownerSheetName, source));
        },
        clearFormula(cellIndex) {
            return formulaBindingEffect('Failed to clear formula', () => clearFormulaNow(cellIndex));
        },
        invalidateFormula(cellIndex) {
            return formulaBindingEffect('Failed to invalidate formula', () => {
                invalidateFormulaNow(cellIndex);
            });
        },
        rewriteCellFormulasForSheetRename(oldSheetName, newSheetName, formulaChangedCount) {
            return formulaBindingEffect('Failed to rewrite formulas for sheet rename', () => rewriteCellFormulasForSheetRenameNow(oldSheetName, newSheetName, formulaChangedCount));
        },
        rebuildAllFormulaBindings() {
            return formulaBindingEffect('Failed to rebuild formula bindings', () => rebuildAllFormulaBindingsNow({
                serviceArgs: args,
                clearFormulaBookkeepingNow,
                bindFormulaNow,
                invalidateFormulaNow,
                isCellIndexMappedNow,
                pruneOrphanedDependencyCells,
            }));
        },
        rebindFormulaCells(candidates, formulaChangedCount) {
            return formulaBindingEffect('Failed to rebind formula cells', () => rebindFormulaCellsNow(candidates, formulaChangedCount));
        },
        rebindDefinedNameDependents(names, formulaChangedCount) {
            return formulaBindingEffect('Failed to rebind defined name dependents', () => rebindTrackedDependentsNow(args.reverseState.reverseDefinedNameEdges, names.map((name) => normalizeDefinedName(name)), formulaChangedCount));
        },
        rebindTableDependents(tableNames, formulaChangedCount) {
            return formulaBindingEffect('Failed to rebind table dependents', () => rebindTrackedDependentsNow(args.reverseState.reverseTableEdges, tableNames, formulaChangedCount));
        },
        rebindFormulasForSheet(sheetName, formulaChangedCount, candidates) {
            return formulaBindingEffect('Failed to rebind formulas for sheet', () => rebindFormulasForSheetNow(sheetName, formulaChangedCount, candidates));
        },
        bindFormulaNow,
        bindPreparedFormulaNow,
        bindFreshDirectAggregateFormulaRunNow(run) {
            bindFreshDirectAggregateFormulaRun({
                serviceArgs: args,
                edgeArena: args.edgeArena,
                formulaMemberCounts,
                trackFormulaSheetIndexes,
                run,
            });
        },
        bindFreshDirectScalarFormulaRunNow(run) {
            bindFreshDirectScalarFormulaRun({
                serviceArgs: args,
                edgeArena: args.edgeArena,
                formulaMemberCounts,
                appendKnownUniqueReverseEdge,
                trackFormulaSheetIndexes,
                trackFormulaSheetOwnerRun,
                run,
            });
        },
        rewriteFormulaSourcePreservingBindingNow,
        rewriteFormulaCompiledPreservingBindingNow,
        rewriteFormulaMetadataPreservingRuntimeNow,
        deferCellFormulasForSheetRenameNow,
        rewriteCellFormulasForSheetRenameNow,
        retargetDirectAggregateFormulaForStructuralTransformNow,
        retargetDirectAggregateFormulasForStructuralTransformNow,
        bindInitialFormulaNow,
        withInitialFormulaCellsNow: (cellIndices, callback) => pendingInitialFormulaCells.withCells(cellIndices, callback),
        clearFormulaNow,
        invalidateFormulaNow,
        clearFormulaBookkeepingNow,
        deferFormulaFamilyIndexRebuildNow() {
            formulaFamilyIndex.deferRebuildNow();
        },
        deferFormulaFamilyIndexRunsNow(runs) {
            formulaFamilyIndex.deferRunsNow(runs);
        },
        registerFreshFormulaFamilyRunNow(run) {
            return formulaFamilyIndex.registerFreshFormulaFamilyRunNow(run);
        },
        upsertFormulaFamilyRunNow(run) {
            formulaFamilyIndex.upsertFormulaFamilyRunNow(run);
        },
        deferFormulaInstanceTableRebuildNow: formulaInstanceTableRebuild.deferRebuildNow,
        upsertFreshFormulaInstancesNow: formulaInstanceTableRebuild.upsertFreshFormulaInstancesNow,
        hydrateFreshFormulaInstancesNow: formulaInstanceTableRebuild.hydrateFreshFormulaInstancesNow,
        exportFormulaInstancesNow: formulaInstanceTableRebuild.exportFormulaInstancesNow,
        refreshRangeDependenciesNow,
        retargetRangeDependenciesNow,
        rebindFormulaCellsNow,
        rebindDefinedNameDependentsNow(names, formulaChangedCount) {
            return rebindFormulaCellsNow(collectTrackedDependents(args.reverseState.reverseDefinedNameEdges, names), formulaChangedCount);
        },
        rebindTableDependentsNow(tableNames, formulaChangedCount) {
            const normalized = tableNames.map((name) => tableDependencyKey(name));
            return rebindFormulaCellsNow(collectTrackedDependents(args.reverseState.reverseTableEdges, normalized), formulaChangedCount);
        },
        rebindFormulasForSheetNow,
        forEachFormulaCellOwnedBySheetNow(sheetName, fn) {
            formulaSheetIndex.getOwnedBySheetSet(sheetName)?.forEach(fn);
        },
        countFormulaSheetMembersNow(sheetId) {
            return formulaMemberCounts.countSheetMembers(sheetId);
        },
        countFormulaFamilySheetMembersNow(sheetId) {
            return formulaFamilyIndex.countSheetMembersNow(sheetId);
        },
        canUseFormulaFamilyIndexNow() {
            return formulaFamilyIndex.canUseNow();
        },
        isFormulaFamilyIndexReadyNow() {
            return formulaFamilyIndex.isReadyNow();
        },
        tryDeferFormulaFamilyStructuralSourceTransformsNow(sheetId, transform, canDeferCellIndex) {
            return formulaFamilyIndex.tryDeferStructuralSourceTransformsNow(sheetId, transform, canDeferCellIndex);
        },
        forEachFormulaFamilyNow(fn) {
            formulaFamilyIndex.forEachFamilyNow(fn);
        },
        setFormulaFamilyStructuralSourceTransformNow(familyId, transform) {
            formulaFamilyIndex.setStructuralSourceTransformNow(familyId, transform);
        },
        getFormulaFamilyStructuralSourceTransformNow(cellIndex) {
            return formulaFamilyIndex.getStructuralSourceTransformNow(cellIndex);
        },
        hasFormulaFamilyStructuralSourceTransformsNow() {
            return formulaFamilyIndex.hasStructuralSourceTransformsNow();
        },
        peekFormulaFamilyStructuralSourceTransformsNow() {
            return formulaFamilyIndex.peekStructuralSourceTransformsNow();
        },
        consumeFormulaFamilyStructuralSourceTransformsNow() {
            return formulaFamilyIndex.consumeStructuralSourceTransformsNow();
        },
        collectFormulaCellsOwnedBySheetNow(sheetName) {
            return formulaSheetIndex.collectOwnedBySheet(sheetName);
        },
        collectFormulaCellsReferencingSheetNow(sheetName) {
            return formulaSheetIndex.collectReferencingSheet(sheetName);
        },
        collectFormulaCellsForDefinedNamesNow(names) {
            return collectTrackedDependents(args.reverseState.reverseDefinedNameEdges, names.map((name) => normalizeDefinedName(name)));
        },
        collectFormulaCellsForTablesNow(tableNames) {
            return collectTrackedDependents(args.reverseState.reverseTableEdges, tableNames.map((name) => tableDependencyKey(name)));
        },
        getFormulaFamilyStatsNow() {
            formulaFamilyIndex.ensureNow();
            return args.formulaFamilies.getStats();
        },
    };
}
//# sourceMappingURL=formula-binding-service.js.map