import { parseCellAddress } from '@bilig/formula';
import { collectDefinedFormulaNames, collectPreservedUnsupportedFormulaCacheKeys, formulaHasPreservedUnsupportedDependencyCache, formulaShouldPreserveCachedUnsupportedFunctionValueOnFullRecalc, } from './unsupported-formula-cache.js';
import { createWrittenColumnTracker, markWrittenColumn, materializeWrittenColumns, } from '../written-column-tracker.js';
import { pushRuntimeImageCachedFormulaRef } from './runtime-image-cached-formula-refs.js';
import { getOrCreateSheetFormulaMap, toFormulaInstanceKey } from './runtime-image-formula-map.js';
import { attachRuntimeFormulaFamilyRunHints, selectRuntimeFormulaFamilyRunHints } from './runtime-image-formula-family-run-hints.js';
import { restoreAlignedRuntimeFormulaFamilyRuns } from './runtime-image-formula-family-runs.js';
import { attachDenseFreshRuntimeCells, attachDenseFreshRuntimeCellIdentities, getDenseRuntimeSheetRestorePlan, getDenseSnapshotSheetRestoreRuns, releaseRestoredSheetCells, shouldReleaseImportedSourceSnapshotCells, } from './runtime-image-dense-snapshot-restore.js';
import { formulaCachedLiteralToRestoredValue, restoreFreshRuntimeLiteralCell, restoreLiteralCell } from './runtime-image-literal-restore.js';
import { restoreVisualMetadata, restoreWorkbookStructure } from './runtime-image-metadata-restore.js';
import { readLazySheetCellReader } from './runtime-image-lazy-sheet-cell-reader.js';
function formulaValueMatchesInstance(record, value) {
    return value !== undefined && value.sheetName === record.sheetName && value.row === record.row && value.col === record.col;
}
function formulaValuesAreAligned(instances, values) {
    if (instances.length !== values.length) {
        return false;
    }
    for (let index = 0; index < instances.length; index += 1) {
        if (!formulaValueMatchesInstance(instances[index], values[index])) {
            return false;
        }
    }
    return true;
}
function compareFormulaInstanceToRowCol(record, row, col) {
    return record.row - row || record.col - col;
}
function buildRuntimeFormulaSheetSpans(records) {
    const spans = new Map();
    let index = 0;
    while (index < records.length) {
        const first = records[index];
        const sheetName = first.sheetName;
        const start = index;
        index += 1;
        while (index < records.length && records[index].sheetName === sheetName) {
            index += 1;
        }
        spans.set(sheetName, { start, end: index });
    }
    return spans;
}
function hasSnapshotCoordinate(value) {
    return typeof value === 'number' && Number.isInteger(value) && value >= 0;
}
function readRestoredCellCoordinates(sheetName, cell) {
    if (hasSnapshotCoordinate(cell.row) && hasSnapshotCoordinate(cell.col)) {
        return {
            row: cell.row,
            col: cell.col,
        };
    }
    const parsed = parseCellAddress(cell.address, sheetName);
    return {
        row: parsed.row,
        col: parsed.col,
    };
}
class RestoredFormulaSourceRefTable {
    sheetIds;
    cellIndices;
    rows;
    cols;
    sources;
    reusable = {
        sheetId: 0,
        cellIndex: 0,
        row: 0,
        col: 0,
        source: '',
    };
    length = 0;
    constructor(capacity) {
        this.sheetIds = new Uint32Array(capacity);
        this.cellIndices = new Uint32Array(capacity);
        this.rows = new Uint32Array(capacity);
        this.cols = new Uint32Array(capacity);
        this.sources = [];
    }
    push(sheetId, cellIndex, row, col, source) {
        const index = this.length;
        this.sheetIds[index] = sheetId;
        this.cellIndices[index] = cellIndex;
        this.rows[index] = row;
        this.cols[index] = col;
        this.sources[index] = source;
        this.length = index + 1;
    }
    at(index) {
        this.reusable.sheetId = this.sheetIds[index];
        this.reusable.cellIndex = this.cellIndices[index];
        this.reusable.row = this.rows[index];
        this.reusable.col = this.cols[index];
        this.reusable.source = this.sources[index];
        return this.reusable;
    }
}
class RestoredHydratedPreparedFormulaRefTable {
    sheetIds;
    cellIndices;
    rows;
    cols;
    templateIds;
    sources;
    compiled;
    values;
    preserveCachedValueOnFullRecalc;
    freshFormulaInstances;
    freshFormulaFamilyRuns;
    freshFormulaFamilyRunFallbackCount = 0;
    reusable;
    length = 0;
    constructor(capacity, freshFormulaInstances) {
        this.sheetIds = new Uint32Array(capacity);
        this.cellIndices = new Uint32Array(capacity);
        this.rows = new Uint32Array(capacity);
        this.cols = new Uint32Array(capacity);
        this.templateIds = new Int32Array(capacity);
        this.templateIds.fill(-1);
        this.sources = [];
        this.compiled = [];
        this.values = [];
        this.preserveCachedValueOnFullRecalc = new Uint8Array(capacity);
        this.freshFormulaInstances = freshFormulaInstances;
    }
    push(sheetId, cellIndex, row, col, source, compiled, templateId, value, runtimeImageCellIndex, preserveCachedValueOnFullRecalc = false) {
        const index = this.length;
        if (this.freshFormulaInstances !== undefined && runtimeImageCellIndex !== cellIndex) {
            this.freshFormulaInstances = undefined;
        }
        this.sheetIds[index] = sheetId;
        this.cellIndices[index] = cellIndex;
        this.rows[index] = row;
        this.cols[index] = col;
        this.templateIds[index] = templateId ?? -1;
        this.sources[index] = source;
        this.compiled[index] = compiled;
        this.values[index] = value;
        this.preserveCachedValueOnFullRecalc[index] = preserveCachedValueOnFullRecalc ? 1 : 0;
        this.length = index + 1;
    }
    at(index) {
        const reusable = this.reusable ??
            (this.reusable = {
                sheetId: 0,
                row: 0,
                col: 0,
                source: '',
                compiled: this.compiled[index],
                value: this.values[index],
            });
        reusable.sheetId = this.sheetIds[index];
        reusable.cellIndex = this.cellIndices[index];
        reusable.row = this.rows[index];
        reusable.col = this.cols[index];
        reusable.source = this.sources[index];
        reusable.compiled = this.compiled[index];
        const templateId = this.templateIds[index];
        if (templateId === -1) {
            delete reusable.templateId;
        }
        else {
            reusable.templateId = templateId;
        }
        reusable.value = this.values[index];
        if (this.preserveCachedValueOnFullRecalc[index] === 1) {
            reusable.preserveCachedValueOnFullRecalc = true;
        }
        else {
            delete reusable.preserveCachedValueOnFullRecalc;
        }
        return reusable;
    }
    *[Symbol.iterator]() {
        for (let index = 0; index < this.length; index += 1) {
            const templateId = this.templateIds[index];
            yield {
                sheetId: this.sheetIds[index],
                cellIndex: this.cellIndices[index],
                row: this.rows[index],
                col: this.cols[index],
                source: this.sources[index],
                compiled: this.compiled[index],
                ...(templateId === -1 ? {} : { templateId }),
                value: this.values[index],
                ...(this.preserveCachedValueOnFullRecalc[index] === 1 ? { preserveCachedValueOnFullRecalc: true } : {}),
            };
        }
    }
}
function preparedRuntimeFormulaCellIndices(refs) {
    const cellIndices = new Uint32Array(refs.length);
    for (let index = 0; index < refs.length; index += 1) {
        const cellIndex = refs[index].cellIndex;
        if (cellIndex === undefined) {
            return new Uint32Array(0);
        }
        cellIndices[index] = cellIndex;
    }
    return cellIndices;
}
function isFreshRuntimeLogicalSheetInternals(value) {
    return typeof value === 'object' && value !== null;
}
function createFreshRuntimeCellAttacher(workbook, sheet) {
    const logicalCandidate = sheet.logical;
    const logical = isFreshRuntimeLogicalSheetInternals(logicalCandidate) ? logicalCandidate : undefined;
    const attachFreshVisibleCellIdentity = logical?.setFreshVisibleCellIdentityWithAxisIdsDeferred?.bind(logical);
    if (attachFreshVisibleCellIdentity) {
        logical?.deferVisibleCellPageRebuild?.();
        const setGridCell = sheet.grid.createRowMajorSetter();
        return (row, col, cellIndex, rowId, colId) => {
            attachFreshVisibleCellIdentity(cellIndex, rowId, colId);
            setGridCell(row, col, cellIndex);
        };
    }
    const attachFreshVisibleCell = logical?.setFreshVisibleCellWithAxisIdsDeferred?.bind(logical);
    if (!attachFreshVisibleCell) {
        return (row, col, cellIndex, rowId, colId) => {
            workbook.attachAllocatedCellWithLogicalAxisIds(sheet.id, row, col, cellIndex, rowId, colId);
        };
    }
    const setGridCell = sheet.grid.createRowMajorSetter();
    return (row, col, cellIndex, rowId, colId) => {
        attachFreshVisibleCell(row, col, cellIndex, rowId, colId);
        setGridCell(row, col, cellIndex);
    };
}
export function restoreWorkbookFromSnapshot(args) {
    const orderedSheets = restoreWorkbookStructure(args);
    args.checkEvaluationBudget?.();
    const potentialNewCells = orderedSheets.reduce((count, sheet) => count + sheet.cells.length, 0);
    const shouldReleaseSheetCells = shouldReleaseImportedSourceSnapshotCells(args.snapshot);
    const formulaRefs = [];
    const formulaSourceRefs = args.initializeFormulaSourcesAt ? new RestoredFormulaSourceRefTable(potentialNewCells) : undefined;
    const hydratedPreparedFormulaRefs = [];
    const cachedFormulaRefs = [];
    const canHydratePreparedCachedFormulaValues = args.initializeHydratedPreparedCellFormulasAt && args.resolveTemplateForCell;
    const canHydrateImportedCachedFormulaValues = args.initializeCachedFormulaSourcesAt !== undefined;
    const shouldHydrateIterativeFormulaValues = args.snapshot.workbook.metadata?.calculationSettings?.iterate === true;
    const shouldHydrateImportedCachedFormulaValues = args.snapshot.workbook.metadata?.calculationSettings?.fullCalcOnLoad === false ||
        args.snapshot.workbook.metadata?.calculationSettings?.mode === 'manual';
    const preservedUnsupportedFormulaCacheKeys = collectPreservedUnsupportedFormulaCacheKeys(args.snapshot);
    const restoredStringIds = new Map();
    args.checkEvaluationBudget?.();
    args.workbook.cellStore.ensureCapacity(args.workbook.cellStore.size + potentialNewCells);
    const previousOnSetValue = args.workbook.cellStore.onSetValue;
    args.workbook.cellStore.onSetValue = null;
    args.workbook.withBatchedColumnVersionUpdates(() => {
        try {
            for (let sheetIndex = 0; sheetIndex < orderedSheets.length; sheetIndex += 1) {
                args.checkEvaluationBudget?.();
                const sheet = orderedSheets[sheetIndex];
                const sheetRecord = args.workbook.getSheet(sheet.name);
                if (!sheetRecord) {
                    throw new Error(`Missing restore sheet: ${sheet.name}`);
                }
                const sheetId = sheetRecord.id;
                const rowIds = [];
                const colIds = [];
                const ensureRowId = args.workbook.createLogicalAxisIdEnsurer(sheetId, 'row');
                const ensureColId = args.workbook.createLogicalAxisIdEnsurer(sheetId, 'column');
                let literalColumns;
                const restoreFreshCell = (cell, coords, restoredCellIndex) => {
                    if (cell.formula !== undefined) {
                        let hydratedCachedFormula = false;
                        const shouldPreserveCachedUnsupportedValue = cell.value !== undefined &&
                            formulaHasPreservedUnsupportedDependencyCache(preservedUnsupportedFormulaCacheKeys, sheet.name, cell.address, cell.formula);
                        if (canHydrateImportedCachedFormulaValues && shouldHydrateImportedCachedFormulaValues && cell.value !== undefined) {
                            pushRuntimeImageCachedFormulaRef(cachedFormulaRefs, sheetId, coords.row, coords.col, restoredCellIndex, cell.formula, formulaCachedLiteralToRestoredValue(cell.value, args.strings, restoredStringIds));
                            hydratedCachedFormula = true;
                        }
                        else if (canHydratePreparedCachedFormulaValues &&
                            cell.value !== undefined &&
                            (shouldHydrateIterativeFormulaValues || shouldPreserveCachedUnsupportedValue)) {
                            try {
                                const template = args.resolveTemplateForCell(cell.formula, coords.row, coords.col);
                                if (!template.compiled.volatile && !template.compiled.producesSpill) {
                                    hydratedPreparedFormulaRefs.push({
                                        sheetId,
                                        row: coords.row,
                                        col: coords.col,
                                        cellIndex: restoredCellIndex,
                                        source: cell.formula,
                                        compiled: template.compiled,
                                        templateId: template.templateId,
                                        value: formulaCachedLiteralToRestoredValue(cell.value, args.strings, restoredStringIds),
                                        ...(shouldPreserveCachedUnsupportedValue ? { preserveCachedValueOnFullRecalc: true } : {}),
                                    });
                                    hydratedCachedFormula = true;
                                }
                            }
                            catch {
                                if (shouldPreserveCachedUnsupportedValue) {
                                    pushRuntimeImageCachedFormulaRef(cachedFormulaRefs, sheetId, coords.row, coords.col, restoredCellIndex, cell.formula, formulaCachedLiteralToRestoredValue(cell.value, args.strings, restoredStringIds));
                                    hydratedCachedFormula = true;
                                }
                            }
                        }
                        if (!hydratedCachedFormula) {
                            if (formulaSourceRefs) {
                                formulaSourceRefs.push(sheetId, restoredCellIndex, coords.row, coords.col, cell.formula);
                            }
                            else {
                                formulaRefs.push({
                                    sheetId,
                                    cellIndex: restoredCellIndex,
                                    mutation: {
                                        kind: 'setCellFormula',
                                        row: coords.row,
                                        col: coords.col,
                                        formula: cell.formula,
                                    },
                                });
                            }
                        }
                    }
                    else {
                        restoreLiteralCell(args.workbook, args.strings, restoredCellIndex, cell.value ?? null, restoredStringIds);
                        literalColumns ??= createWrittenColumnTracker();
                        markWrittenColumn(literalColumns, coords.col);
                    }
                    if (cell.format !== undefined) {
                        args.workbook.setCellFormat(restoredCellIndex, cell.format);
                    }
                };
                const denseRuns = getDenseSnapshotSheetRestoreRuns(sheet);
                if (denseRuns) {
                    const setGridCell = sheetRecord.grid.createRowMajorSetter();
                    for (const run of denseRuns) {
                        args.checkEvaluationBudget?.();
                        const firstCellIndex = args.workbook.cellStore.size;
                        const runRowIds = run.rows.map((row) => (rowIds[row] ??= ensureRowId(row)));
                        const runColIds = run.cols.map((col) => (colIds[col] ??= ensureColId(col)));
                        const attachedDenseIdentities = attachDenseFreshRuntimeCellIdentities(sheetRecord, firstCellIndex, runRowIds, runColIds);
                        if (!attachedDenseIdentities) {
                            throw new Error(`Dense snapshot restore is unavailable for sheet: ${sheet.name}`);
                        }
                        let sheetCellIndex = run.startIndex;
                        for (const row of run.rows) {
                            args.checkEvaluationBudget?.();
                            for (const col of run.cols) {
                                const cell = sheet.cells[sheetCellIndex];
                                const restoredCellIndex = args.workbook.cellStore.allocateReserved(sheetId, row, col);
                                setGridCell(row, col, restoredCellIndex);
                                restoreFreshCell(cell, { row, col }, restoredCellIndex);
                                sheetCellIndex += 1;
                            }
                        }
                    }
                }
                else {
                    const readLazyCell = readLazySheetCellReader(sheet.cells);
                    const attachFreshCell = createFreshRuntimeCellAttacher(args.workbook, sheetRecord);
                    for (let cellIndex = 0; cellIndex < sheet.cells.length; cellIndex += 1) {
                        args.checkEvaluationBudget?.();
                        const lazyCell = readLazyCell?.(cellIndex);
                        const cell = lazyCell?.cell ?? sheet.cells[cellIndex];
                        const coords = lazyCell && hasSnapshotCoordinate(lazyCell.row) && hasSnapshotCoordinate(lazyCell.col)
                            ? { row: lazyCell.row, col: lazyCell.col }
                            : readRestoredCellCoordinates(sheet.name, cell);
                        const restoredCellIndex = args.workbook.cellStore.allocateReserved(sheetId, coords.row, coords.col);
                        const rowId = (rowIds[coords.row] ??= ensureRowId(coords.row));
                        const colId = (colIds[coords.col] ??= ensureColId(coords.col));
                        attachFreshCell(coords.row, coords.col, restoredCellIndex, rowId, colId);
                        restoreFreshCell(cell, coords, restoredCellIndex);
                    }
                }
                if (literalColumns && literalColumns.count > 0) {
                    args.workbook.notifyColumnsWritten(sheetId, materializeWrittenColumns(literalColumns));
                }
                releaseRestoredSheetCells(sheet, shouldReleaseSheetCells);
            }
        }
        finally {
            args.workbook.cellStore.onSetValue = previousOnSetValue;
        }
    });
    if (hydratedPreparedFormulaRefs.length > 0 && args.initializeHydratedPreparedCellFormulasAt) {
        args.checkEvaluationBudget?.();
        args.initializeHydratedPreparedCellFormulasAt(hydratedPreparedFormulaRefs, hydratedPreparedFormulaRefs.length);
    }
    if (cachedFormulaRefs.length > 0 && args.initializeCachedFormulaSourcesAt) {
        args.checkEvaluationBudget?.();
        args.initializeCachedFormulaSourcesAt(cachedFormulaRefs, cachedFormulaRefs.length);
    }
    if (formulaSourceRefs && formulaSourceRefs.length > 0) {
        args.checkEvaluationBudget?.();
        args.initializeFormulaSourcesAt(formulaSourceRefs, formulaSourceRefs.length);
    }
    else if (formulaRefs.length > 0) {
        args.checkEvaluationBudget?.();
        args.initializeCellFormulasAt(formulaRefs, formulaRefs.length);
    }
    args.checkEvaluationBudget?.();
    restoreVisualMetadata({
        workbook: args.workbook,
        workbookMetadata: args.snapshot.workbook.metadata,
    });
    return { formulaCount: hydratedPreparedFormulaRefs.length + (formulaSourceRefs?.length ?? formulaRefs.length) };
}
export function restoreWorkbookFromRuntimeImage(args) {
    const orderedSheets = restoreWorkbookStructure(args);
    const sheetIdsByName = new Map();
    args.checkEvaluationBudget?.();
    args.hydrateTemplateBank(args.runtimeImage.templateBank);
    args.checkEvaluationBudget?.();
    const formulaValueIndexAligned = formulaValuesAreAligned(args.runtimeImage.formulaInstances, args.runtimeImage.formulaValues);
    const formulaValuesByAddress = formulaValueIndexAligned ? undefined : new Map();
    if (formulaValuesByAddress) {
        args.runtimeImage.formulaValues.forEach((record) => {
            args.checkEvaluationBudget?.();
            getOrCreateSheetFormulaMap(formulaValuesByAddress, record.sheetName).set(toFormulaInstanceKey(record.row, record.col), record.value);
        });
    }
    args.checkEvaluationBudget?.();
    const formulaSpansBySheet = buildRuntimeFormulaSheetSpans(args.runtimeImage.formulaInstances);
    const sheetCellsByName = new Map((args.runtimeImage.sheetCells ?? []).map((record) => [record.sheetName, record]));
    const totalCellCount = orderedSheets.reduce((sum, sheet) => sum + sheet.cells.length, 0);
    if (totalCellCount > 0) {
        args.checkEvaluationBudget?.();
        args.workbook.cellStore.ensureCapacity(args.workbook.cellStore.size + totalCellCount);
    }
    const formulaRefs = [];
    const formulaSourceRefs = args.initializeFormulaSourcesAt ? new RestoredFormulaSourceRefTable(totalCellCount) : undefined;
    const preparedFormulaRefs = [];
    let preparedFormulaRefsAreRuntimeCellIndexAligned = true;
    const hydratedPreparedFormulaRefs = new RestoredHydratedPreparedFormulaRefTable(totalCellCount, args.runtimeImage.formulaInstances);
    const cachedFormulaRefs = [];
    const canHydratePreparedSnapshotFormulaValues = args.initializeHydratedPreparedCellFormulasAt && args.resolveTemplateForCell;
    const canHydrateImportedCachedSnapshotFormulaValues = args.initializeCachedFormulaSourcesAt !== undefined;
    const shouldHydrateIterativeFormulaValues = args.snapshot.workbook.metadata?.calculationSettings?.iterate === true;
    const shouldHydrateImportedCachedFormulaValues = args.snapshot.workbook.metadata?.calculationSettings?.fullCalcOnLoad === false ||
        args.snapshot.workbook.metadata?.calculationSettings?.mode === 'manual';
    const definedFormulaNames = shouldHydrateIterativeFormulaValues ? undefined : collectDefinedFormulaNames(args.snapshot);
    const preservedUnsupportedFormulaCacheKeys = collectPreservedUnsupportedFormulaCacheKeys(args.snapshot);
    const restoredStringIds = new Map();
    const previousOnSetValue = args.workbook.cellStore.onSetValue;
    args.workbook.cellStore.onSetValue = null;
    args.workbook.withBatchedColumnVersionUpdates(() => {
        try {
            orderedSheets.forEach((sheet) => {
                args.checkEvaluationBudget?.();
                const sheetRecord = args.workbook.getSheet(sheet.name);
                if (!sheetRecord) {
                    throw new Error(`Missing runtime restore sheet: ${sheet.name}`);
                }
                const sheetId = sheetRecord.id;
                sheetIdsByName.set(sheet.name, sheetId);
                const sheetCellSnapshot = sheetCellsByName.get(sheet.name);
                const sheetCoords = sheetCellSnapshot?.coords;
                const rowIds = [];
                const colIds = [];
                const ensureRowId = args.workbook.createLogicalAxisIdEnsurer(sheetId, 'row');
                const ensureColId = args.workbook.createLogicalAxisIdEnsurer(sheetId, 'column');
                let attachFreshCell;
                const getFreshCellAttacher = () => {
                    attachFreshCell ??= createFreshRuntimeCellAttacher(args.workbook, sheetRecord);
                    return attachFreshCell;
                };
                const formulaSpan = formulaSpansBySheet.get(sheet.name);
                let formulaInstanceIndex = formulaSpan?.start ?? 0;
                const formulaInstanceEnd = formulaSpan?.end ?? formulaInstanceIndex;
                let literalColumns;
                const restoreRuntimeCell = (cell, row, col, cellIndex) => {
                    while (formulaInstanceIndex < formulaInstanceEnd &&
                        compareFormulaInstanceToRowCol(args.runtimeImage.formulaInstances[formulaInstanceIndex], row, col) < 0) {
                        args.checkEvaluationBudget?.();
                        formulaInstanceIndex += 1;
                    }
                    const candidateFormula = formulaInstanceIndex < formulaInstanceEnd ? args.runtimeImage.formulaInstances[formulaInstanceIndex] : undefined;
                    const restoredFormula = candidateFormula && compareFormulaInstanceToRowCol(candidateFormula, row, col) === 0 ? candidateFormula : undefined;
                    if (cell.formula === undefined && restoredFormula === undefined) {
                        restoreFreshRuntimeLiteralCell(args.workbook.cellStore, args.strings, cellIndex, cell.value, restoredStringIds);
                        literalColumns ??= createWrittenColumnTracker();
                        markWrittenColumn(literalColumns, col);
                    }
                    if (cell.format !== undefined) {
                        args.workbook.setCellFormat(cellIndex, cell.format);
                    }
                    if (restoredFormula) {
                        const cachedValue = formulaValueIndexAligned
                            ? args.runtimeImage.formulaValues[formulaInstanceIndex]?.value
                            : formulaValuesByAddress?.get(sheet.name)?.get(toFormulaInstanceKey(row, col));
                        const shouldPreserveCachedUnsupportedValue = cachedValue !== undefined &&
                            (formulaHasPreservedUnsupportedDependencyCache(preservedUnsupportedFormulaCacheKeys, sheet.name, cell.address, restoredFormula.source) ||
                                (!shouldHydrateIterativeFormulaValues &&
                                    definedFormulaNames !== undefined &&
                                    formulaShouldPreserveCachedUnsupportedFunctionValueOnFullRecalc(restoredFormula.source, definedFormulaNames)));
                        const template = restoredFormula.templateId !== undefined && args.resolveTemplateById
                            ? args.resolveTemplateById(restoredFormula.templateId, restoredFormula.source, row, col)
                            : undefined;
                        if (args.initializeHydratedPreparedCellFormulasAt && cachedValue !== undefined) {
                            if (template && !template.compiled.volatile && !template.compiled.producesSpill) {
                                hydratedPreparedFormulaRefs.push(sheetId, cellIndex, row, col, restoredFormula.source, template.compiled, template.templateId, cachedValue, restoredFormula.cellIndex, shouldPreserveCachedUnsupportedValue);
                                return;
                            }
                        }
                        if (template && args.initializePreparedCellFormulasAt) {
                            if (restoredFormula.cellIndex !== cellIndex) {
                                preparedFormulaRefsAreRuntimeCellIndexAligned = false;
                            }
                            preparedFormulaRefs.push({
                                sheetId,
                                row,
                                col,
                                cellIndex,
                                source: restoredFormula.source,
                                compiled: template.compiled,
                                templateId: template.templateId,
                            });
                            return;
                        }
                        if (formulaSourceRefs) {
                            formulaSourceRefs.push(sheetId, cellIndex, row, col, restoredFormula.source);
                        }
                        else {
                            formulaRefs.push({
                                sheetId,
                                cellIndex,
                                mutation: {
                                    kind: 'setCellFormula',
                                    row,
                                    col,
                                    formula: restoredFormula.source,
                                },
                            });
                        }
                    }
                    else if (cell.formula !== undefined) {
                        let hydratedCachedFormula = false;
                        const shouldPreserveCachedUnsupportedValue = cell.value !== undefined &&
                            (formulaHasPreservedUnsupportedDependencyCache(preservedUnsupportedFormulaCacheKeys, sheet.name, cell.address, cell.formula) ||
                                (canHydratePreparedSnapshotFormulaValues &&
                                    !shouldHydrateIterativeFormulaValues &&
                                    definedFormulaNames !== undefined &&
                                    formulaShouldPreserveCachedUnsupportedFunctionValueOnFullRecalc(cell.formula, definedFormulaNames)));
                        if (canHydrateImportedCachedSnapshotFormulaValues && shouldHydrateImportedCachedFormulaValues && cell.value !== undefined) {
                            pushRuntimeImageCachedFormulaRef(cachedFormulaRefs, sheetId, row, col, cellIndex, cell.formula, formulaCachedLiteralToRestoredValue(cell.value, args.strings, restoredStringIds));
                            hydratedCachedFormula = true;
                        }
                        else if (canHydratePreparedSnapshotFormulaValues &&
                            cell.value !== undefined &&
                            (shouldHydrateIterativeFormulaValues || shouldPreserveCachedUnsupportedValue)) {
                            try {
                                const template = args.resolveTemplateForCell(cell.formula, row, col);
                                if (!template.compiled.volatile && !template.compiled.producesSpill) {
                                    hydratedPreparedFormulaRefs.push(sheetId, cellIndex, row, col, cell.formula, template.compiled, template.templateId, formulaCachedLiteralToRestoredValue(cell.value, args.strings, restoredStringIds), cellIndex, shouldPreserveCachedUnsupportedValue);
                                    hydratedCachedFormula = true;
                                }
                            }
                            catch {
                                if (shouldPreserveCachedUnsupportedValue) {
                                    pushRuntimeImageCachedFormulaRef(cachedFormulaRefs, sheetId, row, col, cellIndex, cell.formula, formulaCachedLiteralToRestoredValue(cell.value, args.strings, restoredStringIds));
                                    hydratedCachedFormula = true;
                                }
                            }
                        }
                        if (!hydratedCachedFormula) {
                            if (formulaSourceRefs) {
                                formulaSourceRefs.push(sheetId, cellIndex, row, col, cell.formula);
                            }
                            else {
                                formulaRefs.push({
                                    sheetId,
                                    cellIndex,
                                    mutation: {
                                        kind: 'setCellFormula',
                                        row,
                                        col,
                                        formula: cell.formula,
                                    },
                                });
                            }
                        }
                    }
                };
                const denseRestorePlan = getDenseRuntimeSheetRestorePlan(sheet, sheetCellSnapshot);
                if (denseRestorePlan) {
                    const firstCellIndex = args.workbook.cellStore.allocateDenseRowMajorAtReserved(sheetId, 0, denseRestorePlan.height, 0, denseRestorePlan.width);
                    for (let col = 0; col < denseRestorePlan.width; col += 1) {
                        colIds[col] = ensureColId(col);
                    }
                    for (let row = 0; row < denseRestorePlan.height; row += 1) {
                        rowIds[row] = ensureRowId(row);
                    }
                    const attachedDenseCells = attachDenseFreshRuntimeCells(sheetRecord, firstCellIndex, 0, 0, rowIds, colIds);
                    let index = 0;
                    if (attachedDenseCells) {
                        for (let row = 0; row < denseRestorePlan.height; row += 1) {
                            args.checkEvaluationBudget?.();
                            for (let col = 0; col < denseRestorePlan.width; col += 1) {
                                restoreRuntimeCell(sheet.cells[index], row, col, firstCellIndex + index);
                                index += 1;
                            }
                        }
                    }
                    else {
                        const attachCell = getFreshCellAttacher();
                        for (let row = 0; row < denseRestorePlan.height; row += 1) {
                            args.checkEvaluationBudget?.();
                            const rowId = rowIds[row];
                            for (let col = 0; col < denseRestorePlan.width; col += 1) {
                                const cellIndex = firstCellIndex + index;
                                attachCell(row, col, cellIndex, rowId, colIds[col]);
                                restoreRuntimeCell(sheet.cells[index], row, col, cellIndex);
                                index += 1;
                            }
                        }
                    }
                }
                else {
                    const readLazyCell = readLazySheetCellReader(sheet.cells);
                    const attachCell = getFreshCellAttacher();
                    for (let index = 0; index < sheet.cells.length; index += 1) {
                        args.checkEvaluationBudget?.();
                        const lazyCell = readLazyCell?.(index);
                        const cell = lazyCell?.cell ?? sheet.cells[index];
                        const coords = sheetCoords?.[index] ??
                            (lazyCell && hasSnapshotCoordinate(lazyCell.row) && hasSnapshotCoordinate(lazyCell.col)
                                ? { row: lazyCell.row, col: lazyCell.col }
                                : readRestoredCellCoordinates(sheet.name, cell));
                        const cellIndex = args.workbook.cellStore.allocateReserved(sheetId, coords.row, coords.col);
                        const rowId = (rowIds[coords.row] ??= ensureRowId(coords.row));
                        const colId = (colIds[coords.col] ??= ensureColId(coords.col));
                        attachCell(coords.row, coords.col, cellIndex, rowId, colId);
                        restoreRuntimeCell(cell, coords.row, coords.col, cellIndex);
                    }
                }
                if (literalColumns && literalColumns.count > 0) {
                    args.workbook.notifyColumnsWritten(sheetId, materializeWrittenColumns(literalColumns));
                }
            });
        }
        finally {
            args.workbook.cellStore.onSetValue = previousOnSetValue;
        }
    });
    const runtimeFormulaFamilyRunCount = args.runtimeImage.formulaFamilyRuns?.length ?? 0;
    const restoredFormulaFamilyRuns = runtimeFormulaFamilyRunCount === 0
        ? undefined
        : restoreAlignedRuntimeFormulaFamilyRuns({
            runs: args.runtimeImage.formulaFamilyRuns,
            sheetIdsByName,
        });
    if (hydratedPreparedFormulaRefs.length > 0 && args.initializeHydratedPreparedCellFormulasAt) {
        args.checkEvaluationBudget?.();
        const selectedRestoredRuns = selectRuntimeFormulaFamilyRunHints({
            restoredRuns: restoredFormulaFamilyRuns,
            cellIndices: hydratedPreparedFormulaRefs.cellIndices.subarray(0, hydratedPreparedFormulaRefs.length),
        });
        attachRuntimeFormulaFamilyRunHints({
            refs: hydratedPreparedFormulaRefs,
            restoredRuns: hydratedPreparedFormulaRefs.freshFormulaInstances === undefined ? undefined : selectedRestoredRuns,
            runtimeRunCount: selectedRestoredRuns?.runs.length ?? 0,
            cellIndicesAreRuntimeAligned: hydratedPreparedFormulaRefs.freshFormulaInstances !== undefined,
        });
        args.initializeHydratedPreparedCellFormulasAt(hydratedPreparedFormulaRefs, hydratedPreparedFormulaRefs.length);
    }
    if (cachedFormulaRefs.length > 0 && args.initializeCachedFormulaSourcesAt) {
        args.checkEvaluationBudget?.();
        args.initializeCachedFormulaSourcesAt(cachedFormulaRefs, cachedFormulaRefs.length);
    }
    if (preparedFormulaRefs.length > 0 && args.initializePreparedCellFormulasAt) {
        args.checkEvaluationBudget?.();
        const selectedRestoredRuns = selectRuntimeFormulaFamilyRunHints({
            restoredRuns: restoredFormulaFamilyRuns,
            cellIndices: preparedRuntimeFormulaCellIndices(preparedFormulaRefs),
        });
        attachRuntimeFormulaFamilyRunHints({
            refs: preparedFormulaRefs,
            restoredRuns: selectedRestoredRuns,
            runtimeRunCount: selectedRestoredRuns?.runs.length ?? 0,
            cellIndicesAreRuntimeAligned: preparedFormulaRefsAreRuntimeCellIndexAligned,
        });
        args.initializePreparedCellFormulasAt(preparedFormulaRefs, preparedFormulaRefs.length);
    }
    if (formulaSourceRefs && formulaSourceRefs.length > 0) {
        args.checkEvaluationBudget?.();
        args.initializeFormulaSourcesAt(formulaSourceRefs, formulaSourceRefs.length);
    }
    if (formulaRefs.length > 0) {
        args.checkEvaluationBudget?.();
        args.initializeCellFormulasAt(formulaRefs, formulaRefs.length);
    }
    args.checkEvaluationBudget?.();
    restoreVisualMetadata({
        workbook: args.workbook,
        workbookMetadata: args.snapshot.workbook.metadata,
    });
    return {
        formulaCount: hydratedPreparedFormulaRefs.length + preparedFormulaRefs.length + (formulaSourceRefs?.length ?? 0) + formulaRefs.length,
    };
}
//# sourceMappingURL=runtime-image.js.map