import { ValueTag } from '@bilig/protocol';
import { addEngineCounter } from '../../perf/engine-counters.js';
import { evaluateInitialDirectScalar, evaluateInitialDirectScalarNumber } from './formula-initialization-direct-scalar.js';
import { evaluateInitialDirectFormulas, INITIAL_DIRECT_FORMULA_EVALUATION_LIMIT } from './formula-initialization-direct-formulas.js';
import { canEvaluateInitialPrefixAggregateGroupsNatively } from './formula-initialization-prefix-aggregates.js';
import { createDeferredInitialFormulaFamilyRunMap, flushDeferredInitialFormulaFamilyRuns, noteDeferredFormulaFamilyRunMember as noteDeferredFormulaFamilyRunMemberNow, registerDeferredFormulaFamilyRunNow, } from './formula-initialization-family-runs.js';
import { flushAlignedFreshFormulaFamilyRuns, readAlignedFreshFormulaFamilyRunsFromRefs, } from './formula-initialization-restored-family-runs.js';
import { createInitialTemplateFormulaResolver } from './formula-initialization-template-resolver.js';
import { createInitialFormulaValueWriter } from './formula-initialization-value-writer.js';
import { materializeDeferredFormulaInstances, writeDeferredFormulaInstance } from './formula-initialization-fresh-instances.js';
import { initialFormulaEntryRefAt, } from './formula-initialization-refs.js';
import { createInitialFormulaCellIndexPlan } from './formula-initialization-cell-index-plan.js';
import { recalculateFreshVolatileFormulasAfterInitialMaterialization } from './formula-initialization-volatile-pass.js';
import { canEvaluateInitialDirectRuntimeFormula, compiledFormulaRequiresWorkbookMetadataBinding, hasPendingFormulaDependency, } from './formula-initialization-predicates.js';
import { rethrowFatalFormulaBindingError } from './formula-binding-error-policy.js';
import { createInitialNativeDirectScalarBatch, MAX_INITIAL_NATIVE_DIRECT_SCALAR_BATCH_SIZE, MIN_INITIAL_NATIVE_DIRECT_SCALAR_BATCH_SIZE, } from './formula-initialization-native-direct-scalar.js';
import { createInitialDirectScalarRunChunkCollector } from './formula-initialization-direct-scalar-run-chunks.js';
import { createInitialDirectScalarPreEvaluationTracker } from './formula-initialization-direct-scalar-pre-evaluation.js';
import { createInitialNativeDirectScalarRowChainBatch, MIN_INITIAL_NATIVE_DIRECT_SCALAR_ROW_CHAIN_BATCH_SIZE, } from './formula-initialization-native-direct-scalar-row-chain.js';
import { createInitialNativeDirectLookupBatch, MAX_INITIAL_NATIVE_DIRECT_LOOKUP_BATCH_SIZE, MIN_INITIAL_NATIVE_DIRECT_LOOKUP_BATCH_SIZE, } from './formula-initialization-native-direct-lookup.js';
import { createFormulaInitializationSheetNameResolver, scanFormulaInitializationCycleMembers, } from './formula-initialization-service-helpers.js';
import { createInitialFreshAggregateFormulaDependencyIndex } from './formula-initialization-aggregate-dependency-index.js';
import { buildFreshDirectAggregateMember } from './formula-initialization-hydrated-direct-scalar.js';
import { tryEvaluateFormulaLeafInlineScalar } from './formula-leaf-inline-scalar-evaluator.js';
import { initializeCachedFormulaSourcesAtNow as initializeCachedFormulaSourcesAtNowUnchecked } from './formula-initialization-cached-formulas.js';
import { canBindInitialFreshDirectScalarFormula } from './formula-initialization-fresh-direct-scalar-binding.js';
import { createInitialFreshDirectScalarFormulaRunQueue } from './formula-initialization-fresh-direct-scalar-run-queue.js';
import { formulaInitializationMutationEffect } from './formula-initialization-effect.js';
import { initializeHydratedPreparedCellFormulasAtNowUnchecked } from './formula-initialization-hydrated-prepared.js';
export function createEngineFormulaInitializationService(args) {
    const hasCycleMembersNow = () => scanFormulaInitializationCycleMembers(args.state);
    const resolveSheetName = createFormulaInitializationSheetNameResolver(args.state);
    const noteDeferredFormulaFamilyRunMember = (runs, prepared, runtimeFormula) => noteDeferredFormulaFamilyRunMemberNow({ runs, prepared, runtimeFormula });
    const registerDeferredFormulaFamilyRun = (run) => registerDeferredFormulaFamilyRunNow({
        run,
        registerFreshFormulaFamilyRun: args.registerFreshFormulaFamilyRun,
        upsertFormulaFamilyRun: args.upsertFormulaFamilyRun,
    });
    const initializeFormulaEntriesNow = (refs, potentialNewCells, resolveCellIndex, resolveEntry) => {
        if (refs.length === 0) {
            return;
        }
        args.beginEvaluationBudget(performance.now());
        try {
            args.checkEvaluationBudget();
            let mutationCollectionStarted = false;
            const beginMutationCollection = () => {
                if (!mutationCollectionStarted) {
                    args.beginMutationCollection();
                    mutationCollectionStarted = true;
                }
            };
            let hadCycleMembersBefore;
            const hadCycleMembersBeforeNow = () => (hadCycleMembersBefore ??= hasCycleMembersNow());
            let changedInputCount = 0;
            let formulaChangedCount = 0;
            let topologyChanged = false;
            let compileMs = 0;
            let workbookDateSystem;
            const resolveWorkbookDateSystem = () => (workbookDateSystem ??= args.state.workbook.getCalculationSettings().dateSystem);
            const reservedNewCells = potentialNewCells ?? refs.length;
            const hadExistingFormulas = args.state.formulas.size > 0;
            args.state.workbook.cellStore.ensureCapacity(args.state.workbook.cellStore.size + reservedNewCells);
            args.resetMaterializedCellScratch(reservedNewCells);
            const { targetCellIndices, pendingInitialFormulaCellIndices, pendingFormulaCells, maxTargetCellIndex } = createInitialFormulaCellIndexPlan({
                refs,
                hadExistingFormulas,
                resolveCellIndex,
                checkEvaluationBudget: args.checkEvaluationBudget,
            });
            let canAssignTopoInBatch = !hadExistingFormulas;
            let needsFreshTopoRebuild = false;
            let nextTopoRank = 0;
            let orderedPreparedCellIndices = hadExistingFormulas ? [] : undefined;
            let orderedPreparedCellCount = 0;
            let canUseInitialDirectEvaluation = false;
            let allPreparedFormulasCanUseInitialDirectEvaluation = true;
            let hasInitialPrefixAggregateCandidates = false;
            let canUseNativeInitialDirectScalarOverLimit = false;
            let canUseNativeInitialDirectLookupOverLimit = false;
            let canUseNativeInitialPrefixAggregateOverLimit = false;
            let canUseNativeInitialDirectScalarRunChunksOverLimit = false;
            let inlineInitialDirectScalarWriter;
            const nativeInitialDirectScalarRunChunks = hadExistingFormulas || refs.length < MIN_INITIAL_NATIVE_DIRECT_SCALAR_BATCH_SIZE
                ? undefined
                : createInitialDirectScalarRunChunkCollector({ state: args.state });
            let nativeInitialDirectScalarBatch = hadExistingFormulas ||
                refs.length < MIN_INITIAL_NATIVE_DIRECT_SCALAR_BATCH_SIZE ||
                refs.length > MAX_INITIAL_NATIVE_DIRECT_SCALAR_BATCH_SIZE
                ? undefined
                : createInitialNativeDirectScalarBatch({ state: args.state, capacity: refs.length });
            let nativeInitialDirectScalarRowChainBatch = hadExistingFormulas || refs.length < MIN_INITIAL_NATIVE_DIRECT_SCALAR_ROW_CHAIN_BATCH_SIZE || refs.length % 2 !== 0
                ? undefined
                : createInitialNativeDirectScalarRowChainBatch({ state: args.state, capacity: refs.length });
            let nativeInitialDirectScalarCellCount = 0;
            let nativeInitialDirectLookupBatch = hadExistingFormulas ||
                refs.length < MIN_INITIAL_NATIVE_DIRECT_LOOKUP_BATCH_SIZE ||
                refs.length > MAX_INITIAL_NATIVE_DIRECT_LOOKUP_BATCH_SIZE
                ? undefined
                : createInitialNativeDirectLookupBatch({ state: args.state, capacity: refs.length });
            let nativeInitialDirectLookupCellCount = 0;
            const shouldDeferFormulaInstanceTable = !hadExistingFormulas && (args.hydrateFreshFormulaInstances !== undefined || args.deferFormulaInstanceTableRebuild !== undefined);
            const deferredFormulaInstances = !hadExistingFormulas && args.hydrateFreshFormulaInstances !== undefined ? Array(refs.length) : undefined;
            let deferredFormulaInstanceCount = 0;
            const alignedFreshFormulaFamilyRuns = readAlignedFreshFormulaFamilyRunsFromRefs({
                refs,
                hadExistingFormulas,
                counters: args.state.counters,
            });
            const shouldDeferFormulaFamilyIndex = args.deferFormulaFamilyIndexRebuild !== undefined && (!hadExistingFormulas || alignedFreshFormulaFamilyRuns !== undefined);
            const canCaptureDeferredFormulaFamilyRuns = !shouldDeferFormulaFamilyIndex || args.deferFormulaFamilyIndexRuns !== undefined;
            const deferredFormulaFamilyRuns = hadExistingFormulas || alignedFreshFormulaFamilyRuns !== undefined || !canCaptureDeferredFormulaFamilyRuns
                ? undefined
                : createDeferredInitialFormulaFamilyRunMap();
            let freshFormulaChangedBufferMaterialized = hadExistingFormulas;
            const materializeOrderedPreparedCellIndices = () => {
                if (orderedPreparedCellIndices) {
                    return orderedPreparedCellIndices;
                }
                orderedPreparedCellIndices = [];
                for (let index = 0; index < orderedPreparedCellCount; index += 1) {
                    orderedPreparedCellIndices.push(targetCellIndices[index]);
                }
                return orderedPreparedCellIndices;
            };
            const pushOrderedPreparedCellIndex = (cellIndex) => {
                if (!hadExistingFormulas && orderedPreparedCellIndices === undefined) {
                    orderedPreparedCellCount += 1;
                    return;
                }
                materializeOrderedPreparedCellIndices().push(cellIndex);
                orderedPreparedCellCount += 1;
            };
            const noteSkippedOrderedPreparedCellIndex = () => {
                if (!hadExistingFormulas && orderedPreparedCellIndices === undefined) {
                    materializeOrderedPreparedCellIndices();
                }
            };
            const orderedPreparedCellList = () => orderedPreparedCellIndices ?? targetCellIndices;
            const inlineInitialDirectScalarCells = createInitialDirectScalarPreEvaluationTracker({
                state: args.state,
                refsLength: refs.length,
                targetCellIndices,
                maxTargetCellIndex,
                orderedPreparedCellList,
                orderedPreparedCellCount: () => orderedPreparedCellCount,
            });
            const materializeFreshFormulaChangedBuffer = () => {
                if (hadExistingFormulas || freshFormulaChangedBufferMaterialized) {
                    return formulaChangedCount;
                }
                beginMutationCollection();
                const orderedPreparedCells = orderedPreparedCellList();
                for (let index = 0; index < orderedPreparedCellCount; index += 1) {
                    args.checkEvaluationBudget();
                    formulaChangedCount = args.markFormulaChanged(orderedPreparedCells[index], formulaChangedCount);
                }
                freshFormulaChangedBufferMaterialized = true;
                return formulaChangedCount;
            };
            const logicalFormulaChangedCount = () => !hadExistingFormulas && !freshFormulaChangedBufferMaterialized ? orderedPreparedCellCount : formulaChangedCount;
            const noteInlineInitialDirectScalarCell = (prepared, runtimeFormula) => {
                inlineInitialDirectScalarCells.noteCell(prepared.cellIndex, runtimeFormula);
            };
            const tryInlineInitialDirectScalarEvaluation = (prepared, runtimeFormula) => {
                if (hadExistingFormulas ||
                    !canAssignTopoInBatch ||
                    !runtimeFormula ||
                    runtimeFormula.compiled.volatile ||
                    runtimeFormula.compiled.producesSpill) {
                    return;
                }
                if (runtimeFormula.directScalar !== undefined) {
                    const addedToNativeRunChunks = nativeInitialDirectScalarRunChunks?.add({
                        cellIndex: prepared.cellIndex,
                        sheetId: prepared.sheetId,
                        row: prepared.row,
                        col: prepared.col,
                        runtimeFormula,
                    }) === true;
                    let addedToNativeRowChain = false;
                    if (nativeInitialDirectScalarRowChainBatch) {
                        if (nativeInitialDirectScalarRowChainBatch.add(prepared, runtimeFormula)) {
                            addedToNativeRowChain = true;
                        }
                        else {
                            nativeInitialDirectScalarRowChainBatch = undefined;
                        }
                    }
                    if (nativeInitialDirectScalarBatch) {
                        if (nativeInitialDirectScalarBatch.add(prepared, runtimeFormula.directScalar)) {
                            nativeInitialDirectScalarCellCount += 1;
                            return;
                        }
                        nativeInitialDirectScalarBatch = undefined;
                        nativeInitialDirectScalarCellCount = 0;
                    }
                    if (addedToNativeRowChain) {
                        return;
                    }
                    if (addedToNativeRunChunks) {
                        return;
                    }
                    const numericValue = evaluateInitialDirectScalarNumber(args.state, runtimeFormula.directScalar);
                    inlineInitialDirectScalarWriter ??= createInitialFormulaValueWriter(args);
                    if (numericValue !== undefined) {
                        inlineInitialDirectScalarWriter.writeNumberAt(prepared.cellIndex, prepared.sheetId, prepared.col, numericValue);
                        noteInlineInitialDirectScalarCell(prepared, runtimeFormula);
                        return;
                    }
                    const fallbackValue = evaluateInitialDirectScalar(args.state, runtimeFormula.directScalar);
                    if (fallbackValue !== undefined) {
                        inlineInitialDirectScalarWriter.writeValueAt(prepared.cellIndex, prepared.sheetId, prepared.col, fallbackValue);
                        noteInlineInitialDirectScalarCell(prepared, runtimeFormula);
                        return;
                    }
                }
                if (runtimeFormula.directLookup !== undefined && nativeInitialDirectLookupBatch) {
                    if (nativeInitialDirectLookupBatch.add(prepared, runtimeFormula.directLookup)) {
                        nativeInitialDirectLookupCellCount += 1;
                        return;
                    }
                    nativeInitialDirectLookupBatch = undefined;
                    nativeInitialDirectLookupCellCount = 0;
                }
                if (runtimeFormula.inlineScalarFastPlanKind !== undefined) {
                    const inlineValue = tryEvaluateFormulaLeafInlineScalar({
                        state: args.state,
                        formula: runtimeFormula,
                    });
                    if (inlineValue !== undefined) {
                        inlineInitialDirectScalarWriter ??= createInitialFormulaValueWriter(args);
                        if (inlineValue.tag === ValueTag.Number) {
                            inlineInitialDirectScalarWriter.writeNumberAt(prepared.cellIndex, prepared.sheetId, prepared.col, inlineValue.value);
                        }
                        else {
                            inlineInitialDirectScalarWriter.writeValueAt(prepared.cellIndex, prepared.sheetId, prepared.col, inlineValue);
                        }
                        noteInlineInitialDirectScalarCell(prepared, runtimeFormula);
                    }
                }
            };
            const noteBoundFormula = (prepared, runtimeFormula) => {
                if (hadExistingFormulas) {
                    beginMutationCollection();
                    formulaChangedCount = args.markFormulaChanged(prepared.cellIndex, formulaChangedCount);
                }
                topologyChanged = true;
                pushOrderedPreparedCellIndex(prepared.cellIndex);
                if (canAssignTopoInBatch && pendingFormulaCells) {
                    if (!canEvaluateInitialDirectRuntimeFormula(runtimeFormula)) {
                        allPreparedFormulasCanUseInitialDirectEvaluation = false;
                    }
                    if (runtimeFormula?.directAggregate !== undefined) {
                        hasInitialPrefixAggregateCandidates = true;
                    }
                    const hasPendingDependency = runtimeFormula !== undefined &&
                        hasPendingFormulaDependency(runtimeFormula, pendingFormulaCells, (rangeIndex) => args.state.ranges.getMembersView(rangeIndex));
                    if (!runtimeFormula || hasPendingDependency) {
                        needsFreshTopoRebuild ||= hasPendingDependency;
                        canAssignTopoInBatch = false;
                    }
                    else {
                        args.state.workbook.cellStore.topoRanks[prepared.cellIndex] = nextTopoRank;
                        nextTopoRank += 1;
                        tryInlineInitialDirectScalarEvaluation(prepared, runtimeFormula);
                    }
                }
            };
            const noteInitializedFormula = (prepared, runtimeFormula) => {
                if (alignedFreshFormulaFamilyRuns === undefined) {
                    noteDeferredFormulaFamilyRunMember(deferredFormulaFamilyRuns, prepared, runtimeFormula);
                }
                deferredFormulaInstanceCount = writeDeferredFormulaInstance(deferredFormulaInstances, deferredFormulaInstanceCount, prepared, runtimeFormula);
                noteBoundFormula(prepared, runtimeFormula);
            };
            const initialFreshDirectScalarRunQueue = createInitialFreshDirectScalarFormulaRunQueue({
                bindFreshDirectScalarFormulaRun: args.bindFreshDirectScalarFormulaRun,
                capacity: refs.length,
                counters: args.state.counters,
                noteInitializedFormula,
                readRuntimeFormula: (cellIndex) => args.state.formulas.get(cellIndex),
                clearPendingFormulaCell: (cellIndex) => {
                    if (pendingFormulaCells) {
                        pendingFormulaCells.delete(cellIndex);
                    }
                },
            });
            let pendingInitialFreshDirectAggregateRun;
            const flushInitialFreshDirectAggregateRun = () => {
                const run = pendingInitialFreshDirectAggregateRun;
                if (run === undefined) {
                    return;
                }
                pendingInitialFreshDirectAggregateRun = undefined;
                const bindFreshDirectAggregateFormulaRun = args.bindFreshDirectAggregateFormulaRun;
                if (bindFreshDirectAggregateFormulaRun === undefined) {
                    return;
                }
                const bindingInput = run.members.length === 1
                    ? {
                        sheetId: run.sheetId,
                        ownerSheetName: run.ownerSheetName,
                        cellIndex: run.cellIndices[0],
                        member: run.members[0],
                    }
                    : {
                        sheetId: run.sheetId,
                        ownerSheetName: run.ownerSheetName,
                        cellIndices: run.cellIndices,
                        members: run.members,
                    };
                bindFreshDirectAggregateFormulaRun(bindingInput);
                addEngineCounter(args.state.counters, 'initialFreshDirectAggregateFastBindings', run.members.length);
                for (const prepared of run.preparedEntries) {
                    noteInitializedFormula(prepared, args.state.formulas.get(prepared.cellIndex));
                    if (pendingFormulaCells) {
                        pendingFormulaCells.delete(prepared.cellIndex);
                    }
                }
            };
            const queueInitialFreshDirectAggregate = (prepared, member) => {
                if (pendingInitialFreshDirectAggregateRun !== undefined &&
                    (pendingInitialFreshDirectAggregateRun.sheetId !== prepared.sheetId ||
                        pendingInitialFreshDirectAggregateRun.ownerSheetName !== prepared.ownerSheetName)) {
                    flushInitialFreshDirectAggregateRun();
                }
                pendingInitialFreshDirectAggregateRun ??= {
                    sheetId: prepared.sheetId,
                    ownerSheetName: prepared.ownerSheetName,
                    cellIndices: [],
                    members: [],
                    preparedEntries: [],
                };
                pendingInitialFreshDirectAggregateRun.cellIndices.push(prepared.cellIndex);
                pendingInitialFreshDirectAggregateRun.members.push(member);
                pendingInitialFreshDirectAggregateRun.preparedEntries.push(prepared);
            };
            const freshDirectAggregateFormulaDependencyIndex = createInitialFreshAggregateFormulaDependencyIndex({
                pendingFormulaCells,
                state: args.state,
                targetCellIndices,
            });
            args.setBatchMutationDepth(args.getBatchMutationDepth() + 1);
            try {
                args.clearTemplateFormulaCache();
                const compileStarted = performance.now();
                const bindFormulaEntries = () => {
                    args.state.workbook.withBatchedColumnVersionUpdates(() => {
                        for (let refIndex = 0; refIndex < refs.length; refIndex += 1) {
                            args.checkEvaluationBudget();
                            const ref = initialFormulaEntryRefAt(refs, refIndex);
                            const cellIndex = hadExistingFormulas ? pendingInitialFormulaCellIndices[refIndex] : targetCellIndices[refIndex];
                            try {
                                const prepared = resolveEntry(ref, cellIndex);
                                if (canBindInitialFreshDirectScalarFormula({
                                    bindFreshDirectScalarFormulaRun: args.bindFreshDirectScalarFormulaRun,
                                    hadExistingFormulas,
                                    prepared,
                                    refsLength: refs.length,
                                })) {
                                    flushInitialFreshDirectAggregateRun();
                                    initialFreshDirectScalarRunQueue.queue(prepared);
                                    continue;
                                }
                                const freshDirectAggregateMember = !hadExistingFormulas &&
                                    args.bindFreshDirectAggregateFormulaRun !== undefined &&
                                    !compiledFormulaRequiresWorkbookMetadataBinding(prepared.compiled)
                                    ? buildFreshDirectAggregateMember(prepared.ownerSheetName, prepared)
                                    : undefined;
                                if (freshDirectAggregateMember !== undefined &&
                                    !freshDirectAggregateFormulaDependencyIndex.hasFormulaDependency(freshDirectAggregateMember)) {
                                    initialFreshDirectScalarRunQueue.flush();
                                    queueInitialFreshDirectAggregate(prepared, freshDirectAggregateMember);
                                    continue;
                                }
                                flushInitialFreshDirectAggregateRun();
                                initialFreshDirectScalarRunQueue.flush();
                                const requiresWorkbookMetadataBinding = compiledFormulaRequiresWorkbookMetadataBinding(prepared.compiled);
                                if (requiresWorkbookMetadataBinding) {
                                    args.bindFormula(prepared.cellIndex, prepared.ownerSheetName, prepared.source);
                                }
                                else {
                                    args.bindPreparedFormula(prepared.cellIndex, prepared.ownerSheetName, prepared.source, prepared.compiled, prepared.templateId, {
                                        deferFamilyRegistration: shouldDeferFormulaFamilyIndex ||
                                            deferredFormulaFamilyRuns !== undefined ||
                                            alignedFreshFormulaFamilyRuns !== undefined,
                                        deferFormulaInstanceRegistration: shouldDeferFormulaInstanceTable,
                                        assumeFreshFormula: !hadExistingFormulas,
                                        resolveWorkbookDateSystem,
                                        ownerPosition: {
                                            sheetName: prepared.ownerSheetName,
                                            row: prepared.row,
                                            col: prepared.col,
                                        },
                                    });
                                }
                                const runtimeFormula = args.state.formulas.get(prepared.cellIndex);
                                noteInitializedFormula(prepared, runtimeFormula);
                            }
                            catch (error) {
                                rethrowFatalFormulaBindingError(error);
                                noteSkippedOrderedPreparedCellIndex();
                                topologyChanged = args.removeFormula(cellIndex) || topologyChanged;
                                args.setInvalidFormulaValue(cellIndex);
                                beginMutationCollection();
                                changedInputCount = args.markInputChanged(cellIndex, changedInputCount);
                            }
                            if (pendingFormulaCells) {
                                pendingFormulaCells.delete(cellIndex);
                            }
                        }
                        flushInitialFreshDirectAggregateRun();
                        initialFreshDirectScalarRunQueue.flush();
                        if (args.state.ranges.size > 0) {
                            beginMutationCollection();
                            const reboundCount = formulaChangedCount;
                            formulaChangedCount = args.syncDynamicRanges(formulaChangedCount);
                            topologyChanged = topologyChanged || formulaChangedCount !== reboundCount;
                        }
                        if (!flushAlignedFreshFormulaFamilyRuns({
                            runs: alignedFreshFormulaFamilyRuns,
                            shouldDeferFormulaFamilyIndex,
                            deferFormulaFamilyIndexRuns: args.deferFormulaFamilyIndexRuns,
                            deferFormulaFamilyIndexRebuild: args.deferFormulaFamilyIndexRebuild,
                            registerFormulaFamilyRun: registerDeferredFormulaFamilyRun,
                            checkEvaluationBudget: args.checkEvaluationBudget,
                        })) {
                            flushDeferredInitialFormulaFamilyRuns({
                                runs: deferredFormulaFamilyRuns,
                                shouldDeferFormulaFamilyIndex,
                                deferFormulaFamilyIndexRuns: args.deferFormulaFamilyIndexRuns,
                                deferFormulaFamilyIndexRebuild: args.deferFormulaFamilyIndexRebuild,
                                registerFormulaFamilyRun: registerDeferredFormulaFamilyRun,
                            });
                        }
                        if (shouldDeferFormulaInstanceTable) {
                            if (deferredFormulaInstances) {
                                args.hydrateFreshFormulaInstances?.(materializeDeferredFormulaInstances(deferredFormulaInstances, deferredFormulaInstanceCount));
                            }
                            else {
                                args.deferFormulaInstanceTableRebuild?.();
                            }
                        }
                        inlineInitialDirectScalarWriter?.flush();
                    });
                };
                args.withInitialFormulaCells(pendingInitialFormulaCellIndices, bindFormulaEntries);
                const canUseNativeInitialDirectScalarRowChain = !hasInitialPrefixAggregateCandidates &&
                    nativeInitialDirectScalarRowChainBatch !== undefined &&
                    nativeInitialDirectScalarRowChainBatch.count === orderedPreparedCellCount &&
                    orderedPreparedCellCount >= MIN_INITIAL_NATIVE_DIRECT_SCALAR_ROW_CHAIN_BATCH_SIZE;
                canUseNativeInitialDirectScalarOverLimit =
                    !hasInitialPrefixAggregateCandidates &&
                        nativeInitialDirectScalarBatch !== undefined &&
                        nativeInitialDirectScalarBatch.count === orderedPreparedCellCount &&
                        orderedPreparedCellCount > INITIAL_DIRECT_FORMULA_EVALUATION_LIMIT;
                canUseNativeInitialDirectLookupOverLimit =
                    !hasInitialPrefixAggregateCandidates &&
                        nativeInitialDirectLookupBatch !== undefined &&
                        nativeInitialDirectLookupBatch.count === orderedPreparedCellCount &&
                        orderedPreparedCellCount > INITIAL_DIRECT_FORMULA_EVALUATION_LIMIT;
                canUseNativeInitialPrefixAggregateOverLimit =
                    hasInitialPrefixAggregateCandidates &&
                        orderedPreparedCellCount > INITIAL_DIRECT_FORMULA_EVALUATION_LIMIT &&
                        canEvaluateInitialPrefixAggregateGroupsNatively(args, orderedPreparedCellList(), { requireAllCells: true });
                const canUseWholeBatchNativeInitialDirectEvaluation = canUseNativeInitialDirectScalarRowChain ||
                    canUseNativeInitialDirectScalarOverLimit ||
                    canUseNativeInitialDirectLookupOverLimit ||
                    canUseNativeInitialPrefixAggregateOverLimit;
                canUseNativeInitialDirectScalarRunChunksOverLimit =
                    !canUseWholeBatchNativeInitialDirectEvaluation &&
                        nativeInitialDirectScalarRunChunks !== undefined &&
                        orderedPreparedCellCount > INITIAL_DIRECT_FORMULA_EVALUATION_LIMIT &&
                        nativeInitialDirectScalarRunChunks.hasNativeChunks();
                canUseInitialDirectEvaluation =
                    canAssignTopoInBatch &&
                        !hadExistingFormulas &&
                        changedInputCount === 0 &&
                        allPreparedFormulasCanUseInitialDirectEvaluation &&
                        (orderedPreparedCellCount <= INITIAL_DIRECT_FORMULA_EVALUATION_LIMIT ||
                            canUseNativeInitialDirectScalarRowChain ||
                            canUseNativeInitialDirectScalarOverLimit ||
                            canUseNativeInitialDirectLookupOverLimit ||
                            canUseNativeInitialPrefixAggregateOverLimit ||
                            canUseNativeInitialDirectScalarRunChunksOverLimit) &&
                        orderedPreparedCellCount === refs.length;
                compileMs += performance.now() - compileStarted;
            }
            finally {
                args.setBatchMutationDepth(args.getBatchMutationDepth() - 1);
            }
            if (needsFreshTopoRebuild) {
                args.checkEvaluationBudget();
                materializeFreshFormulaChangedBuffer();
                args.prepareRegionQueryIndices();
                args.rebuildTopoRanks();
                args.detectCycles();
                canAssignTopoInBatch = false;
                canUseInitialDirectEvaluation = false;
                args.state.formulas.forEach((_formula, cellIndex) => {
                    if (((args.state.workbook.cellStore.flags[cellIndex] ?? 0) & 8 /* CellFlags.InCycle */) !== 0) {
                        changedInputCount = args.markInputChanged(cellIndex, changedInputCount);
                    }
                });
            }
            else if (topologyChanged && !(canAssignTopoInBatch && !hadExistingFormulas)) {
                args.checkEvaluationBudget();
                materializeFreshFormulaChangedBuffer();
                const repaired = !hadCycleMembersBeforeNow() &&
                    formulaChangedCount > 0 &&
                    args.repairTopoRanks(args.getChangedFormulaBuffer().subarray(0, formulaChangedCount));
                if (!repaired) {
                    args.rebuildTopoRanks();
                    args.detectCycles();
                    args.state.formulas.forEach((_formula, cellIndex) => {
                        if (((args.state.workbook.cellStore.flags[cellIndex] ?? 0) & 8 /* CellFlags.InCycle */) !== 0) {
                            changedInputCount = args.markInputChanged(cellIndex, changedInputCount);
                        }
                    });
                }
            }
            const hasVolatileFormulaWork = args.hasVolatileFormulas?.() !== false;
            if (hasVolatileFormulaWork) {
                materializeFreshFormulaChangedBuffer();
                beginMutationCollection();
                formulaChangedCount = args.markVolatileFormulasChanged(formulaChangedCount);
            }
            const useInitialDirectEvaluation = canUseInitialDirectEvaluation && logicalFormulaChangedCount() === orderedPreparedCellCount;
            if (!useInitialDirectEvaluation) {
                materializeFreshFormulaChangedBuffer();
                args.prepareRegionQueryIndices();
            }
            let recalculated;
            const allowInitialDirectEvaluationOverLimit = canUseNativeInitialDirectScalarOverLimit ||
                canUseNativeInitialDirectLookupOverLimit ||
                canUseNativeInitialPrefixAggregateOverLimit ||
                canUseNativeInitialDirectScalarRunChunksOverLimit;
            const changedInputArrayForRecalc = () => {
                beginMutationCollection();
                return args.getChangedInputBuffer().subarray(0, changedInputCount);
            };
            const recalculateChangedRoots = () => {
                const changedInputArray = changedInputArrayForRecalc();
                const changedRoots = args.composeMutationRoots(changedInputCount, formulaChangedCount);
                return args.recalculate(changedRoots, changedInputArray);
            };
            const recalculatePreorderedChangedRoots = (orderedFormulaCellIndices, orderedFormulaCount) => {
                const changedInputArray = changedInputArrayForRecalc();
                const changedRoots = args.composeMutationRoots(changedInputCount, formulaChangedCount);
                return args.recalculatePreordered(changedRoots, orderedFormulaCellIndices, orderedFormulaCount, changedInputArray);
            };
            const canEvaluateWholeBatchNatively = useInitialDirectEvaluation &&
                !hasInitialPrefixAggregateCandidates &&
                ((nativeInitialDirectScalarRowChainBatch !== undefined &&
                    nativeInitialDirectScalarRowChainBatch.count === orderedPreparedCellCount) ||
                    nativeInitialDirectScalarCellCount === orderedPreparedCellCount ||
                    nativeInitialDirectLookupCellCount === orderedPreparedCellCount);
            if (useInitialDirectEvaluation && !canEvaluateWholeBatchNatively) {
                const runChunkCells = nativeInitialDirectScalarRunChunks?.evaluate();
                if (runChunkCells) {
                    inlineInitialDirectScalarCells.noteReusableCells(runChunkCells);
                }
            }
            if (useInitialDirectEvaluation &&
                nativeInitialDirectScalarRowChainBatch !== undefined &&
                nativeInitialDirectScalarRowChainBatch.count === orderedPreparedCellCount &&
                !hasInitialPrefixAggregateCandidates) {
                const native = nativeInitialDirectScalarRowChainBatch.evaluate();
                if (native) {
                    recalculated = native;
                    args.deferKernelSync(recalculated);
                    addEngineCounter(args.state.counters, 'directFormulaInitialEvaluations', orderedPreparedCellCount);
                }
                else {
                    const direct = evaluateInitialDirectFormulas(args, orderedPreparedCellList(), {
                        alreadyValidated: true,
                        hasPrefixAggregateCandidates: hasInitialPrefixAggregateCandidates,
                        allowOverLimit: allowInitialDirectEvaluationOverLimit,
                    });
                    if (direct) {
                        recalculated = direct;
                    }
                    else {
                        recalculated = recalculateChangedRoots();
                    }
                }
            }
            else if (useInitialDirectEvaluation &&
                nativeInitialDirectScalarCellCount === orderedPreparedCellCount &&
                !hasInitialPrefixAggregateCandidates) {
                const native = nativeInitialDirectScalarBatch?.evaluate();
                if (native) {
                    recalculated = native;
                    args.deferKernelSync(recalculated);
                    addEngineCounter(args.state.counters, 'directFormulaInitialEvaluations', orderedPreparedCellCount);
                }
                else {
                    const direct = evaluateInitialDirectFormulas(args, orderedPreparedCellList(), {
                        alreadyValidated: true,
                        hasPrefixAggregateCandidates: hasInitialPrefixAggregateCandidates,
                        allowOverLimit: allowInitialDirectEvaluationOverLimit,
                    });
                    if (direct) {
                        recalculated = direct;
                    }
                    else {
                        recalculated = recalculateChangedRoots();
                    }
                }
            }
            else if (useInitialDirectEvaluation &&
                nativeInitialDirectLookupCellCount === orderedPreparedCellCount &&
                !hasInitialPrefixAggregateCandidates) {
                const native = nativeInitialDirectLookupBatch?.evaluate();
                if (native) {
                    recalculated = native;
                    args.deferKernelSync(recalculated);
                    addEngineCounter(args.state.counters, 'directFormulaInitialEvaluations', orderedPreparedCellCount);
                }
                else {
                    const direct = evaluateInitialDirectFormulas(args, orderedPreparedCellList(), {
                        alreadyValidated: true,
                        hasPrefixAggregateCandidates: hasInitialPrefixAggregateCandidates,
                        allowOverLimit: allowInitialDirectEvaluationOverLimit,
                    });
                    if (direct) {
                        recalculated = direct;
                    }
                    else {
                        recalculated = recalculateChangedRoots();
                    }
                }
            }
            else if (useInitialDirectEvaluation &&
                inlineInitialDirectScalarCells.cellCount === orderedPreparedCellCount &&
                !hasInitialPrefixAggregateCandidates) {
                recalculated = inlineInitialDirectScalarCells.cellIndices.subarray(0, inlineInitialDirectScalarCells.cellCount);
                args.deferKernelSync(recalculated);
                addEngineCounter(args.state.counters, 'directFormulaInitialEvaluations', orderedPreparedCellCount);
            }
            else if (useInitialDirectEvaluation) {
                const direct = evaluateInitialDirectFormulas(args, orderedPreparedCellList(), {
                    alreadyValidated: true,
                    hasPrefixAggregateCandidates: hasInitialPrefixAggregateCandidates,
                    allowOverLimit: allowInitialDirectEvaluationOverLimit,
                    ...(inlineInitialDirectScalarCells.cellCount > 0
                        ? {
                            preEvaluatedCellIndices: inlineInitialDirectScalarCells.cellIndices,
                            preEvaluatedCellCount: inlineInitialDirectScalarCells.cellCount,
                            preEvaluatedCellsAreReusable: inlineInitialDirectScalarCells.allCellsReusable,
                        }
                        : {}),
                });
                if (direct) {
                    recalculated = direct;
                }
                else {
                    recalculated = recalculateChangedRoots();
                }
            }
            else {
                recalculated =
                    canAssignTopoInBatch && !hadExistingFormulas && orderedPreparedCellCount > 0
                        ? recalculatePreorderedChangedRoots(orderedPreparedCellList(), orderedPreparedCellCount)
                        : recalculateChangedRoots();
            }
            recalculated = args.reconcilePivotOutputs(recalculated, false);
            if (!hadExistingFormulas && hasVolatileFormulaWork) {
                recalculated = recalculateFreshVolatileFormulasAfterInitialMaterialization(args, recalculated);
            }
            void recalculated;
            const lastMetrics = args.state.getLastMetrics();
            args.state.setLastMetrics({
                ...lastMetrics,
                batchId: lastMetrics.batchId + 1,
                changedInputCount: changedInputCount + logicalFormulaChangedCount(),
                compileMs,
            });
        }
        finally {
            args.endEvaluationBudget();
        }
    };
    const initializeCellFormulasAtNow = (refs, potentialNewCells) => {
        const resolveInitialTemplateFormula = createInitialTemplateFormulaResolver(args.compileTemplateFormula);
        initializeFormulaEntriesNow(refs, potentialNewCells, (ref) => {
            if (ref.mutation.kind !== 'setCellFormula') {
                throw new Error('initializeCellFormulasAt only supports setCellFormula coordinate mutations');
            }
            return ref.cellIndex ?? args.ensureCellTrackedByCoords(ref.sheetId, ref.mutation.row, ref.mutation.col);
        }, (ref, cellIndex) => {
            if (ref.mutation.kind !== 'setCellFormula') {
                throw new Error('initializeCellFormulasAt only supports setCellFormula coordinate mutations');
            }
            const ownerSheetName = resolveSheetName(ref.sheetId);
            const template = resolveInitialTemplateFormula(ref.mutation.formula, ref.mutation.row, ref.mutation.col);
            return {
                cellIndex,
                sheetId: ref.sheetId,
                row: ref.mutation.row,
                col: ref.mutation.col,
                ownerSheetName,
                source: ref.mutation.formula,
                compiled: template.compiled,
                templateId: template.templateId,
            };
        });
    };
    const initializeFormulaSourcesAtNow = (refs, potentialNewCells) => {
        const resolveInitialTemplateFormula = createInitialTemplateFormulaResolver(args.compileTemplateFormula);
        initializeFormulaEntriesNow(refs, potentialNewCells, (ref) => ref.cellIndex ?? args.ensureCellTrackedByCoords(ref.sheetId, ref.row, ref.col), (ref, cellIndex) => {
            const ownerSheetName = resolveSheetName(ref.sheetId);
            const template = resolveInitialTemplateFormula(ref.source, ref.row, ref.col);
            return {
                cellIndex,
                sheetId: ref.sheetId,
                row: ref.row,
                col: ref.col,
                ownerSheetName,
                source: ref.source,
                compiled: template.compiled,
                templateId: template.templateId,
            };
        });
    };
    const initializePreparedCellFormulasAtNow = (refs, potentialNewCells) => {
        initializeFormulaEntriesNow(refs, potentialNewCells, (ref) => ref.cellIndex ?? args.ensureCellTrackedByCoords(ref.sheetId, ref.row, ref.col), (ref, cellIndex) => ({
            cellIndex,
            sheetId: ref.sheetId,
            row: ref.row,
            col: ref.col,
            ownerSheetName: resolveSheetName(ref.sheetId),
            source: ref.source,
            compiled: ref.compiled,
            ...(ref.templateId !== undefined ? { templateId: ref.templateId } : {}),
        }));
    };
    const initializeHydratedPreparedCellFormulasAtNow = (refs, potentialNewCells) => {
        if (refs.length === 0) {
            return;
        }
        args.beginEvaluationBudget(performance.now());
        try {
            args.checkEvaluationBudget();
            initializeHydratedPreparedCellFormulasAtNowUnchecked({
                serviceArgs: args,
                refs,
                potentialNewCells,
                hasCycleMembersNow,
                resolveSheetName,
                registerDeferredFormulaFamilyRun,
            });
        }
        finally {
            args.endEvaluationBudget();
        }
    };
    return {
        initializeCellFormulasAt(refs, potentialNewCells) {
            return formulaInitializationMutationEffect('Failed to initialize cell formulas', () => initializeCellFormulasAtNow(refs, potentialNewCells));
        },
        initializePreparedCellFormulasAt(refs, potentialNewCells) {
            return formulaInitializationMutationEffect('Failed to initialize prepared cell formulas', () => initializePreparedCellFormulasAtNow(refs, potentialNewCells));
        },
        initializeHydratedPreparedCellFormulasAt(refs, potentialNewCells) {
            return formulaInitializationMutationEffect('Failed to initialize hydrated prepared cell formulas', () => initializeHydratedPreparedCellFormulasAtNow(refs, potentialNewCells));
        },
        initializeCellFormulasAtNow,
        initializeFormulaSourcesAtNow,
        initializePreparedCellFormulasAtNow,
        initializeHydratedPreparedCellFormulasAtNow,
        initializeCachedFormulaSourcesAtNow: (refs, potentialNewCells) => initializeCachedFormulaSourcesAtNowUnchecked({
            serviceArgs: args,
            refs,
            ...(potentialNewCells === undefined ? {} : { potentialNewCells }),
            resolveSheetName,
        }),
    };
}
//# sourceMappingURL=formula-initialization-service.js.map