import { Effect } from 'effect';
import { ValueTag, } from '@bilig/protocol';
import { cloneCellStyleRecord } from '../../engine-style-utils.js';
import { exportSheetMetadata } from '../../engine-snapshot-utils.js';
import { exportReplicaSnapshot as exportReplicaStateSnapshot, hydrateReplicaState } from '../../replica-state.js';
import { attachRuntimeImage, readRuntimeImage } from '../../snapshot/runtime-image-codec.js';
import { restoreWorkbookFromRuntimeImage, restoreWorkbookFromSnapshot } from '../../snapshot/runtime-image.js';
import { EngineSnapshotError } from '../errors.js';
import { clonePreservedWorkbookMetadata, hasPreservedWorkbookMetadata } from '../../workbook-preserved-metadata.js';
function hasNonDefaultCalculationSettings(calculationSettings) {
    return (calculationSettings.mode !== 'automatic' ||
        calculationSettings.compatibilityMode !== 'excel-modern' ||
        calculationSettings.calcId !== undefined ||
        calculationSettings.iterate !== undefined ||
        calculationSettings.iterateCount !== undefined ||
        calculationSettings.iterateDelta !== undefined ||
        calculationSettings.fullPrecision === false ||
        calculationSettings.fullCalcOnLoad !== undefined ||
        calculationSettings.forceFullCalc !== undefined ||
        calculationSettings.calcOnSave !== undefined ||
        calculationSettings.calcCompleted !== undefined ||
        calculationSettings.concurrentCalc !== undefined ||
        calculationSettings.dateSystem === '1904');
}
function collectExternalCacheExportPlan(args) {
    const cacheSheetNames = new Set([...args.workbook.sheetsByName.values()]
        .filter((sheet) => sheet.name.startsWith('__bilig_ext_') && sheet.visibility === 'veryHidden')
        .map((sheet) => sheet.name));
    if (cacheSheetNames.size === 0) {
        return { cacheSheetNames, omitExternalLinkArtifacts: false };
    }
    const cacheSheetNameList = [...cacheSheetNames];
    let hasLiveCacheReference = false;
    for (const sheet of args.workbook.sheetsByName.values()) {
        if (cacheSheetNames.has(sheet.name)) {
            continue;
        }
        sheet.grid.forEachCellEntry((cellIndex) => {
            if (hasLiveCacheReference) {
                return;
            }
            const formula = args.getCellByIndex(cellIndex).formula;
            if (formula && cacheSheetNameList.some((cacheSheetName) => formula.includes(cacheSheetName))) {
                hasLiveCacheReference = true;
            }
        });
        if (hasLiveCacheReference) {
            break;
        }
    }
    return {
        cacheSheetNames,
        omitExternalLinkArtifacts: !hasLiveCacheReference,
    };
}
function runtimeImageSheetCellsAreDenseRowMajor(args) {
    const { coords, width, height } = args;
    if (!Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0 || coords.length !== width * height) {
        return false;
    }
    for (let index = 0; index < coords.length; index += 1) {
        const coord = coords[index];
        if (coord.row !== Math.floor(index / width) || coord.col !== index % width) {
            return false;
        }
    }
    return true;
}
export function createEngineSnapshotService(args) {
    return {
        exportWorkbook(options = {}) {
            return Effect.try({
                try: () => {
                    const includeRuntimeImage = options.includeRuntimeImage !== false && args.exportTemplateBank !== undefined && args.exportFormulaInstances !== undefined;
                    const workbook = {
                        name: args.state.workbook.workbookName,
                    };
                    const externalCacheExportPlan = collectExternalCacheExportPlan({
                        workbook: args.state.workbook,
                        getCellByIndex: args.getCellByIndex,
                    });
                    const properties = args.state.workbook.listWorkbookProperties().map(({ key, value }) => ({ key, value }));
                    const workbookProtection = args.state.workbook.getWorkbookProtection();
                    const macroPayloads = args.state.workbook.listMacroPayloads().map((payload) => Object.assign({}, payload));
                    const definedNames = args.state.workbook.listDefinedNames().map(({ name, scopeSheetName, value }) => {
                        const snapshot = { name, value };
                        if (scopeSheetName !== undefined) {
                            snapshot.scopeSheetName = scopeSheetName;
                        }
                        return snapshot;
                    });
                    const calculationSettings = args.state.workbook.getCalculationSettings();
                    const volatileContext = args.state.workbook.getVolatileContext();
                    const tables = args.state.workbook.listTables();
                    const spills = args.state.workbook.listSpills().map(({ sheetName, address, rows, cols }) => ({ sheetName, address, rows, cols }));
                    const referencedStyleIds = new Set();
                    const referencedFormatIds = new Set();
                    args.state.workbook.sheetsByName.forEach((sheet) => {
                        sheet.styleRanges.forEach((record) => referencedStyleIds.add(record.styleId));
                        sheet.formatRanges.forEach((record) => referencedFormatIds.add(record.formatId));
                        sheet.grid.forEachCell((cellIndex) => {
                            const explicitFormat = args.state.workbook.getCellFormat(cellIndex);
                            if (explicitFormat !== undefined) {
                                referencedFormatIds.add(args.state.workbook.internCellNumberFormat(explicitFormat).id);
                            }
                        });
                    });
                    const styles = args.state.workbook
                        .listCellStyles()
                        .filter((style) => referencedStyleIds.has(style.id))
                        .map((style) => cloneCellStyleRecord(style));
                    const formats = args.state.workbook
                        .listCellNumberFormats()
                        .filter((format) => referencedFormatIds.has(format.id))
                        .map((format) => Object.assign({}, format));
                    const pivots = args.state.workbook.listPivots().map((pivot) => structuredClone(pivot));
                    const charts = args.state.workbook.listCharts().map((chart) => structuredClone(chart));
                    const drawingArtifacts = args.state.workbook.getDrawingArtifacts();
                    const controlArtifacts = args.state.workbook.metadata.controlArtifacts;
                    const externalLinkArtifacts = externalCacheExportPlan.omitExternalLinkArtifacts
                        ? undefined
                        : args.state.workbook.getExternalLinkArtifacts();
                    const threadedCommentArtifacts = args.state.workbook.getThreadedCommentArtifacts();
                    const cellMetadata = args.state.workbook.metadata.cellMetadata;
                    const preservedWorkbookMetadata = clonePreservedWorkbookMetadata(args.state.workbook.metadata.preservedWorkbookMetadata);
                    if (externalCacheExportPlan.omitExternalLinkArtifacts) {
                        delete preservedWorkbookMetadata.externalWorkbookReferences;
                    }
                    const images = args.state.workbook.listImages().map((image) => structuredClone(image));
                    const shapes = args.state.workbook.listShapes().map((shape) => structuredClone(shape));
                    if (properties.length > 0 ||
                        workbookProtection !== undefined ||
                        macroPayloads.length > 0 ||
                        definedNames.length > 0 ||
                        tables.length > 0 ||
                        spills.length > 0 ||
                        pivots.length > 0 ||
                        charts.length > 0 ||
                        drawingArtifacts !== undefined ||
                        controlArtifacts !== undefined ||
                        externalLinkArtifacts !== undefined ||
                        threadedCommentArtifacts !== undefined ||
                        cellMetadata !== undefined ||
                        hasPreservedWorkbookMetadata(preservedWorkbookMetadata) ||
                        images.length > 0 ||
                        shapes.length > 0 ||
                        styles.length > 0 ||
                        formats.length > 0 ||
                        hasNonDefaultCalculationSettings(calculationSettings) ||
                        volatileContext.recalcEpoch !== 0) {
                        workbook.metadata = {};
                        Object.assign(workbook.metadata, preservedWorkbookMetadata);
                        if (properties.length > 0) {
                            workbook.metadata.properties = properties;
                        }
                        if (workbookProtection) {
                            workbook.metadata.workbookProtection = workbookProtection;
                        }
                        if (macroPayloads.length > 0) {
                            workbook.metadata.macroPayloads = macroPayloads;
                        }
                        if (definedNames.length > 0) {
                            workbook.metadata.definedNames = definedNames;
                        }
                        if (tables.length > 0) {
                            workbook.metadata.tables = tables;
                        }
                        if (spills.length > 0) {
                            workbook.metadata.spills = spills;
                        }
                        if (pivots.length > 0) {
                            workbook.metadata.pivots = pivots;
                        }
                        if (charts.length > 0) {
                            workbook.metadata.charts = charts;
                        }
                        if (drawingArtifacts) {
                            workbook.metadata.drawingArtifacts = drawingArtifacts;
                        }
                        if (controlArtifacts) {
                            workbook.metadata.controlArtifacts = structuredClone(controlArtifacts);
                        }
                        if (externalLinkArtifacts) {
                            workbook.metadata.externalLinkArtifacts = externalLinkArtifacts;
                        }
                        if (threadedCommentArtifacts) {
                            workbook.metadata.threadedCommentArtifacts = threadedCommentArtifacts;
                        }
                        if (cellMetadata) {
                            workbook.metadata.cellMetadata = structuredClone(cellMetadata);
                        }
                        if (images.length > 0) {
                            workbook.metadata.images = images;
                        }
                        if (shapes.length > 0) {
                            workbook.metadata.shapes = shapes;
                        }
                        if (styles.length > 0) {
                            workbook.metadata.styles = styles;
                        }
                        if (formats.length > 0) {
                            workbook.metadata.formats = formats;
                        }
                        if (hasNonDefaultCalculationSettings(calculationSettings)) {
                            workbook.metadata.calculationSettings = calculationSettings;
                        }
                        if (volatileContext.recalcEpoch !== 0) {
                            workbook.metadata.volatileContext = volatileContext;
                        }
                    }
                    const runtimeImageSheetCells = [];
                    const runtimeImageCellValues = [];
                    const workbookSnapshot = {
                        version: 1,
                        workbook,
                        sheets: [...args.state.workbook.sheetsByName.values()]
                            .toSorted((left, right) => left.order - right.order)
                            .filter((sheet) => !externalCacheExportPlan.omitExternalLinkArtifacts || !externalCacheExportPlan.cacheSheetNames.has(sheet.name))
                            .map((sheet) => {
                            const metadata = exportSheetMetadata(args.state.workbook, sheet.name);
                            const cells = [];
                            const sheetCellCoords = [];
                            let materializedWidth = 0;
                            let materializedHeight = 0;
                            sheet.grid.forEachCellEntry((cellIndex, row, col) => {
                                const cellSnapshot = args.getCellByIndex(cellIndex);
                                const explicitFormat = args.state.workbook.getCellFormat(cellIndex);
                                if (includeRuntimeImage && cellSnapshot.value.tag !== ValueTag.Empty) {
                                    runtimeImageCellValues.push({
                                        sheetName: sheet.name,
                                        row,
                                        col,
                                        value: cellSnapshot.value,
                                    });
                                }
                                if ((cellSnapshot.flags & (64 /* CellFlags.SpillChild */ | 128 /* CellFlags.PivotOutput */)) !== 0) {
                                    return;
                                }
                                if (cellSnapshot.formula === undefined &&
                                    explicitFormat === undefined &&
                                    (cellSnapshot.flags & 256 /* CellFlags.AuthoredBlank */) === 0 &&
                                    (cellSnapshot.value.tag === ValueTag.Empty || cellSnapshot.value.tag === ValueTag.Error)) {
                                    return;
                                }
                                const cell = {
                                    address: cellSnapshot.address,
                                };
                                if (explicitFormat !== undefined) {
                                    cell.format = explicitFormat;
                                }
                                if (cellSnapshot.formula) {
                                    cell.formula = cellSnapshot.formula;
                                    if (!includeRuntimeImage) {
                                        if (cellSnapshot.value.tag === ValueTag.Number) {
                                            cell.value = cellSnapshot.value.value;
                                        }
                                        else if (cellSnapshot.value.tag === ValueTag.Boolean) {
                                            cell.value = cellSnapshot.value.value;
                                        }
                                        else if (cellSnapshot.value.tag === ValueTag.String) {
                                            cell.value = cellSnapshot.value.value;
                                        }
                                        else if (cellSnapshot.value.tag === ValueTag.Empty) {
                                            cell.value = null;
                                        }
                                    }
                                }
                                else if (cellSnapshot.value.tag === ValueTag.Number) {
                                    cell.value = cellSnapshot.value.value;
                                }
                                else if (cellSnapshot.value.tag === ValueTag.Boolean) {
                                    cell.value = cellSnapshot.value.value;
                                }
                                else if (cellSnapshot.value.tag === ValueTag.String) {
                                    cell.value = cellSnapshot.value.value;
                                }
                                else {
                                    cell.value = null;
                                }
                                cells.push(cell);
                                if (includeRuntimeImage) {
                                    sheetCellCoords.push({ row, col });
                                    materializedHeight = Math.max(materializedHeight, row + 1);
                                    materializedWidth = Math.max(materializedWidth, col + 1);
                                }
                            });
                            if (includeRuntimeImage) {
                                const coordinateOrder = runtimeImageSheetCellsAreDenseRowMajor({
                                    coords: sheetCellCoords,
                                    width: materializedWidth,
                                    height: materializedHeight,
                                })
                                    ? 'dense-row-major'
                                    : undefined;
                                runtimeImageSheetCells.push({
                                    sheetName: sheet.name,
                                    coords: sheetCellCoords,
                                    ...(coordinateOrder === undefined ? {} : { coordinateOrder }),
                                    dimensions: { width: materializedWidth, height: materializedHeight },
                                    cellCount: sheetCellCoords.length,
                                });
                            }
                            return metadata
                                ? { id: sheet.id, name: sheet.name, order: sheet.order, metadata, cells }
                                : { id: sheet.id, name: sheet.name, order: sheet.order, cells };
                        }),
                    };
                    if (includeRuntimeImage) {
                        const formulaInstances = args.exportFormulaInstances();
                        attachRuntimeImage(workbookSnapshot, {
                            version: 1,
                            templateBank: args.exportTemplateBank(),
                            formulaInstances,
                            formulaValues: formulaInstances.map((record) => ({
                                sheetName: record.sheetName,
                                row: record.row,
                                col: record.col,
                                value: args.getCellByIndex(record.cellIndex).value,
                            })),
                            ...(runtimeImageCellValues.length > 0 ? { cellValues: runtimeImageCellValues } : {}),
                            ...(args.exportFormulaFamilyRuns ? { formulaFamilyRuns: args.exportFormulaFamilyRuns() } : {}),
                            sheetCells: runtimeImageSheetCells,
                        });
                    }
                    return workbookSnapshot;
                },
                catch: (cause) => new EngineSnapshotError({
                    message: 'Failed to export workbook snapshot',
                    cause,
                }),
            });
        },
        importWorkbook(snapshot) {
            return Effect.try({
                try: () => {
                    args.beginEvaluationBudget?.(performance.now());
                    try {
                        args.checkEvaluationBudget?.();
                        const materializeImportedPivots = () => {
                            if (!args.materializePivot) {
                                return;
                            }
                            args.state.workbook.listPivots().forEach((pivot) => {
                                args.checkEvaluationBudget?.();
                                args.materializePivot(pivot);
                            });
                            snapshot.workbook.metadata?.pivots?.forEach((pivot) => {
                                args.checkEvaluationBudget?.();
                                args.state.workbook.setPivot(pivot);
                            });
                        };
                        const runtimeImage = readRuntimeImage(snapshot);
                        if (runtimeImage && args.hydrateTemplateBank) {
                            const restoreResult = restoreWorkbookFromRuntimeImage({
                                snapshot,
                                runtimeImage,
                                workbook: args.state.workbook,
                                strings: args.state.strings,
                                resetWorkbook: args.resetWorkbook,
                                ...(args.checkEvaluationBudget ? { checkEvaluationBudget: args.checkEvaluationBudget } : {}),
                                hydrateTemplateBank: args.hydrateTemplateBank,
                                initializeCellFormulasAt: args.initializeCellFormulasAt,
                                ...(args.resolveTemplateById ? { resolveTemplateById: args.resolveTemplateById } : {}),
                                ...(args.initializeFormulaSourcesAt ? { initializeFormulaSourcesAt: args.initializeFormulaSourcesAt } : {}),
                                ...(args.resolveTemplateForCell ? { resolveTemplateForCell: args.resolveTemplateForCell } : {}),
                                ...(args.initializePreparedCellFormulasAt
                                    ? { initializePreparedCellFormulasAt: args.initializePreparedCellFormulasAt }
                                    : {}),
                                ...(args.initializeHydratedPreparedCellFormulasAt
                                    ? { initializeHydratedPreparedCellFormulasAt: args.initializeHydratedPreparedCellFormulasAt }
                                    : {}),
                                ...(args.initializeCachedFormulaSourcesAt
                                    ? { initializeCachedFormulaSourcesAt: args.initializeCachedFormulaSourcesAt }
                                    : {}),
                            });
                            args.checkEvaluationBudget?.();
                            materializeImportedPivots();
                            args.emitFullInvalidation?.({ incrementMetrics: restoreResult.formulaCount === 0 });
                            return;
                        }
                        const restoreResult = restoreWorkbookFromSnapshot({
                            snapshot,
                            workbook: args.state.workbook,
                            strings: args.state.strings,
                            resetWorkbook: args.resetWorkbook,
                            ...(args.checkEvaluationBudget ? { checkEvaluationBudget: args.checkEvaluationBudget } : {}),
                            initializeCellFormulasAt: args.initializeCellFormulasAt,
                            ...(args.initializeFormulaSourcesAt ? { initializeFormulaSourcesAt: args.initializeFormulaSourcesAt } : {}),
                            ...(args.resolveTemplateForCell ? { resolveTemplateForCell: args.resolveTemplateForCell } : {}),
                            ...(args.initializeHydratedPreparedCellFormulasAt
                                ? { initializeHydratedPreparedCellFormulasAt: args.initializeHydratedPreparedCellFormulasAt }
                                : {}),
                            ...(args.initializeCachedFormulaSourcesAt ? { initializeCachedFormulaSourcesAt: args.initializeCachedFormulaSourcesAt } : {}),
                        });
                        args.checkEvaluationBudget?.();
                        materializeImportedPivots();
                        args.emitFullInvalidation?.({ incrementMetrics: restoreResult.formulaCount === 0 });
                    }
                    finally {
                        args.endEvaluationBudget?.();
                    }
                },
                catch: (cause) => new EngineSnapshotError({
                    message: 'Failed to import workbook snapshot',
                    cause,
                }),
            });
        },
        exportReplica() {
            return Effect.try({
                try: () => ({
                    replica: exportReplicaStateSnapshot(args.state.replicaState),
                    entityVersions: [...args.state.entityVersions.entries()].map(([entityKey, order]) => ({
                        entityKey,
                        order,
                    })),
                    sheetDeleteVersions: [...args.state.sheetDeleteVersions.entries()].map(([sheetName, order]) => ({
                        sheetName,
                        order,
                    })),
                }),
                catch: (cause) => new EngineSnapshotError({
                    message: 'Failed to export replica snapshot',
                    cause,
                }),
            });
        },
        importReplica(snapshot) {
            return Effect.try({
                try: () => {
                    hydrateReplicaState(args.state.replicaState, snapshot.replica);
                    args.state.entityVersions.clear();
                    snapshot.entityVersions.forEach(({ entityKey, order }) => {
                        args.state.entityVersions.set(entityKey, order);
                    });
                    args.state.sheetDeleteVersions.clear();
                    snapshot.sheetDeleteVersions.forEach(({ sheetName, order }) => {
                        args.state.sheetDeleteVersions.set(sheetName, order);
                    });
                },
                catch: (cause) => new EngineSnapshotError({
                    message: 'Failed to import replica snapshot',
                    cause,
                }),
            });
        },
    };
}
//# sourceMappingURL=snapshot-service.js.map