import { ValueTag } from '@bilig/protocol';
import { renameFormulaSheetReferences, rewriteFormulaForStructuralTransform } from '@bilig/formula';
import { mapStructuralAxisIndex, mapStructuralAxisInterval, structuralTransformForOp } from '../../engine-structural-utils.js';
import { sheetMetadataToOps } from '../../engine-snapshot-utils.js';
import { addEngineCounter } from '../../perf/engine-counters.js';
import { getRuntimeFormulaSource } from '../runtime-formula-source.js';
import { dependencyTouchesStructuralDeleteSpan, structuralDeletedCellUndoRecordToOps, structuralFormulaUndoRecordToOp, } from './mutation-structural-undo-records.js';
import { captureStructuralWorkbookMetadataOps, clearStructuralSheetMetadataOps } from './mutation-structural-metadata-ops.js';
function captureRuntimeFormulaSource(args, cellIndex, formula, hasFamilyTransforms = args.hasFormulaFamilyStructuralSourceTransforms?.() === true) {
    const familyTransform = hasFamilyTransforms ? args.getFormulaFamilyStructuralSourceTransform?.(cellIndex) : undefined;
    return getRuntimeFormulaSource(formula, familyTransform);
}
function cloneStructuralSourceTransform(transform) {
    if (!transform) {
        return undefined;
    }
    return {
        ownerSheetName: transform.ownerSheetName,
        targetSheetName: transform.targetSheetName,
        transform: { ...transform.transform },
        preservesValue: transform.preservesValue,
    };
}
function captureRuntimeFormulaSourceHandle(args, cellIndex, formula, hasFamilyTransforms = args.hasFormulaFamilyStructuralSourceTransforms?.() === true) {
    const ownStructuralSourceTransform = cloneStructuralSourceTransform(formula.structuralSourceTransform);
    const captured = {
        source: formula.source,
    };
    const withOwnTransform = ownStructuralSourceTransform === undefined ? captured : { ...captured, structuralSourceTransform: ownStructuralSourceTransform };
    const withSingleRenameTransform = formula.sourceRenameTransform === undefined
        ? withOwnTransform
        : {
            ...withOwnTransform,
            sourceRenameTransform: {
                oldSheetName: formula.sourceRenameTransform.oldSheetName,
                newSheetName: formula.sourceRenameTransform.newSheetName,
            },
        };
    const withRenameTransforms = formula.sourceRenameTransforms === undefined
        ? withSingleRenameTransform
        : {
            ...withSingleRenameTransform,
            sourceRenameTransforms: formula.sourceRenameTransforms.map((transform) => ({ ...transform })),
        };
    if (ownStructuralSourceTransform !== undefined) {
        return withRenameTransforms;
    }
    const inheritedStructuralSourceTransform = hasFamilyTransforms
        ? cloneStructuralSourceTransform(args.getFormulaFamilyStructuralSourceTransform?.(cellIndex))
        : undefined;
    return inheritedStructuralSourceTransform === undefined
        ? withRenameTransforms
        : { ...withRenameTransforms, inheritedStructuralSourceTransform };
}
function materializeRuntimeFormulaSource(captured) {
    let source = captured.source;
    if (captured.sourceRenameTransform !== undefined) {
        source = renameFormulaSheetReferences(source, captured.sourceRenameTransform.oldSheetName, captured.sourceRenameTransform.newSheetName);
    }
    captured.sourceRenameTransforms?.forEach((transform) => {
        source = renameFormulaSheetReferences(source, transform.oldSheetName, transform.newSheetName);
    });
    const deferred = captured.structuralSourceTransform ?? captured.inheritedStructuralSourceTransform;
    if (!deferred) {
        return source;
    }
    return rewriteFormulaForStructuralTransform(source, deferred.ownerSheetName, deferred.targetSheetName, deferred.transform);
}
function structuralFormulaUndoCaptureToOp(capture) {
    return structuralFormulaUndoRecordToOp({
        sheetName: capture.sheetName,
        row: capture.row,
        col: capture.col,
        formula: materializeRuntimeFormulaSource(capture.source),
    });
}
function canRestoreDirectAggregateThroughStructuralInverse(args) {
    if (args.axis !== 'row' || args.ownerSheetName !== args.sheetName || args.ownerAxisIndex === undefined) {
        return false;
    }
    const directAggregate = args.formula.directAggregate;
    if (!directAggregate ||
        directAggregate.sheetName !== args.sheetName ||
        directAggregate.aggregateKind !== 'sum' ||
        args.formula.compiled.symbolicNames.length > 0 ||
        args.formula.compiled.symbolicTables.length > 0 ||
        args.formula.compiled.symbolicSpills.length > 0) {
        return false;
    }
    const transform = { kind: 'delete', axis: 'row', start: args.start, count: args.count };
    const deleteEnd = args.start + args.count - 1;
    const deleteOverlapsAggregate = directAggregate.rowStart <= deleteEnd && directAggregate.rowEnd >= args.start;
    const deleteTouchesAggregateBoundary = directAggregate.rowStart >= args.start || directAggregate.rowEnd <= deleteEnd;
    if (deleteOverlapsAggregate && deleteTouchesAggregateBoundary) {
        return false;
    }
    return (mapStructuralAxisIndex(args.ownerAxisIndex, transform) !== undefined &&
        mapStructuralAxisInterval(directAggregate.rowStart, directAggregate.rowEnd, transform) !== undefined);
}
function directFormulaRangeTouchesStructuralDeleteSpan(formula, sheetName, axis, start, count) {
    const deleteEnd = start + count - 1;
    const aggregate = formula.directAggregate;
    if (aggregate?.sheetName === sheetName) {
        return axis === 'row'
            ? aggregate.rowStart <= deleteEnd && aggregate.rowEnd >= start
            : aggregate.col <= deleteEnd && aggregate.colEnd >= start;
    }
    const criteria = formula.directCriteria;
    if (!criteria) {
        return false;
    }
    const directCriteriaRanges = [
        ...(criteria.aggregateRange ? [criteria.aggregateRange] : []),
        ...criteria.criteriaPairs.map((pair) => pair.range),
    ];
    return directCriteriaRanges.some((range) => {
        if (range.sheetName !== sheetName) {
            return false;
        }
        return axis === 'row' ? range.rowStart <= deleteEnd && range.rowEnd >= start : range.col >= start && range.col <= deleteEnd;
    });
}
export function createMutationStructuralDeleteInverseHelpers(args) {
    const createSheetNameReader = () => {
        const sheetNameById = new Map();
        return (sheetId) => {
            if (sheetNameById.has(sheetId)) {
                return sheetNameById.get(sheetId);
            }
            const sheetName = args.state.workbook.getSheetNameById(sheetId);
            sheetNameById.set(sheetId, sheetName);
            return sheetName;
        };
    };
    const createCellPositionReader = () => {
        const sheetStructureVersionById = new Map();
        const getSheetStructureVersionById = (sheetId) => {
            if (sheetStructureVersionById.has(sheetId)) {
                return sheetStructureVersionById.get(sheetId);
            }
            const structureVersion = args.state.workbook.getSheetById(sheetId)?.structureVersion;
            sheetStructureVersionById.set(sheetId, structureVersion);
            return structureVersion;
        };
        return (cellIndex) => {
            const sheetId = args.state.workbook.cellStore.sheetIds[cellIndex];
            if (sheetId === undefined || sheetId === 0) {
                return undefined;
            }
            if (getSheetStructureVersionById(sheetId) === 1) {
                const row = args.state.workbook.cellStore.rows[cellIndex];
                const col = args.state.workbook.cellStore.cols[cellIndex];
                return row === undefined || col === undefined ? undefined : { sheetId, row, col };
            }
            return args.state.workbook.getCellPosition(cellIndex);
        };
    };
    const captureFormulaCellRecordsForStructuralUndo = (sheetName, axis, start, count, options = {}) => {
        const captured = [];
        const hasFamilyTransforms = args.hasFormulaFamilyStructuralSourceTransforms?.() === true;
        const getSheetNameById = createSheetNameReader();
        const readCellPosition = createCellPositionReader();
        args.state.formulas.forEach((formula, cellIndex) => {
            const ownerSheetId = args.state.workbook.cellStore.sheetIds[cellIndex];
            if (ownerSheetId === undefined) {
                return;
            }
            const ownerSheetName = getSheetNameById(ownerSheetId);
            if (!ownerSheetName) {
                return;
            }
            const ownerPosition = readCellPosition(cellIndex);
            const axisIndex = axis === 'row' ? ownerPosition?.row : ownerPosition?.col;
            if (ownerSheetName === sheetName && axisIndex !== undefined && axisIndex >= start && axisIndex < start + count) {
                return;
            }
            const ownerPositionAffected = ownerSheetName === sheetName && axisIndex !== undefined && axisIndex >= start + count;
            if (options.skipRedundantDirectAggregateCaptures === true &&
                canRestoreDirectAggregateThroughStructuralInverse({
                    formula,
                    ownerSheetName,
                    ownerAxisIndex: axisIndex,
                    sheetName,
                    axis,
                    start,
                    count,
                })) {
                return;
            }
            let dependencyPositionAffected = false;
            if (!ownerPositionAffected) {
                if (formula.compiled.deps.length > 0) {
                    addEngineCounter(args.state.counters, 'structuralUndoFormulaDependencyScans');
                }
                dependencyPositionAffected = formula.compiled.deps.some((dependency) => dependencyTouchesStructuralDeleteSpan(dependency, ownerSheetName, sheetName, axis, start));
            }
            const directFormulaRangeAffected = directFormulaRangeTouchesStructuralDeleteSpan(formula, sheetName, axis, start, count);
            const metadataSensitive = formula.compiled.symbolicNames.length > 0 ||
                formula.compiled.symbolicTables.length > 0 ||
                formula.compiled.symbolicSpills.length > 0;
            if (!ownerPositionAffected && !dependencyPositionAffected && !directFormulaRangeAffected && !metadataSensitive) {
                return;
            }
            captured.push({
                sheetName: ownerSheetName,
                row: ownerPosition?.row ?? 0,
                col: ownerPosition?.col ?? 0,
                source: captureRuntimeFormulaSourceHandle(args, cellIndex, formula, hasFamilyTransforms),
            });
        });
        return captured;
    };
    const captureFormulaCellStateForStructuralUndo = (sheetName, axis, start, count) => captureFormulaCellRecordsForStructuralUndo(sheetName, axis, start, count).map(structuralFormulaUndoCaptureToOp);
    const captureFormulaCellRecordsForStructuralInsertUndo = (sheetName, axis, start) => {
        const captured = [];
        const hasFamilyTransforms = args.hasFormulaFamilyStructuralSourceTransforms?.() === true;
        const getSheetNameById = createSheetNameReader();
        const readCellPosition = createCellPositionReader();
        args.state.formulas.forEach((formula, cellIndex) => {
            const ownerSheetId = args.state.workbook.cellStore.sheetIds[cellIndex];
            if (ownerSheetId === undefined) {
                return;
            }
            const ownerSheetName = getSheetNameById(ownerSheetId);
            if (!ownerSheetName) {
                return;
            }
            if (axis === 'column' &&
                formula.structuralSourceTransform === undefined &&
                formula.sourceRenameTransform === undefined &&
                formula.sourceRenameTransforms === undefined) {
                return;
            }
            const ownerPosition = readCellPosition(cellIndex);
            const axisIndex = axis === 'row' ? ownerPosition?.row : ownerPosition?.col;
            const ownerPositionAffected = ownerSheetName === sheetName && axisIndex !== undefined && axisIndex >= start;
            let dependencyPositionAffected = false;
            if (!ownerPositionAffected) {
                if (formula.compiled.deps.length > 0) {
                    addEngineCounter(args.state.counters, 'structuralUndoFormulaDependencyScans');
                }
                dependencyPositionAffected = formula.compiled.deps.some((dependency) => dependencyTouchesStructuralDeleteSpan(dependency, ownerSheetName, sheetName, axis, start));
            }
            const directFormulaRangeAffected = directFormulaRangeTouchesStructuralDeleteSpan(formula, sheetName, axis, start, 1);
            const metadataSensitive = formula.compiled.symbolicNames.length > 0 ||
                formula.compiled.symbolicTables.length > 0 ||
                formula.compiled.symbolicSpills.length > 0;
            if (!ownerPositionAffected && !dependencyPositionAffected && !directFormulaRangeAffected && !metadataSensitive) {
                return;
            }
            captured.push({
                sheetName: ownerSheetName,
                row: ownerPosition?.row ?? 0,
                col: ownerPosition?.col ?? 0,
                source: captureRuntimeFormulaSourceHandle(args, cellIndex, formula, hasFamilyTransforms),
            });
        });
        return captured;
    };
    const captureFormulaCellStateForStructuralInsertUndo = (sheetName, axis, start, _count) => captureFormulaCellRecordsForStructuralInsertUndo(sheetName, axis, start).map(structuralFormulaUndoCaptureToOp);
    const captureDeletedCellUndoRecord = (cellIndex, sheetName, row, col) => {
        const flags = args.state.workbook.cellStore.flags[cellIndex] ?? 0;
        if ((flags & (64 /* CellFlags.SpillChild */ | 128 /* CellFlags.PivotOutput */)) !== 0) {
            return undefined;
        }
        const explicitFormat = args.state.workbook.getCellFormat(cellIndex);
        const formula = args.state.formulas.get(cellIndex);
        if (formula) {
            const hasFamilyTransforms = args.hasFormulaFamilyStructuralSourceTransforms?.() === true;
            return {
                kind: 'formula',
                sheetName,
                row,
                col,
                formula: captureRuntimeFormulaSource(args, cellIndex, formula, hasFamilyTransforms),
                ...(explicitFormat === undefined ? {} : { explicitFormat }),
            };
        }
        const tag = (args.state.workbook.cellStore.tags[cellIndex] ?? ValueTag.Empty);
        if (explicitFormat === undefined && (flags & 256 /* CellFlags.AuthoredBlank */) === 0 && (tag === ValueTag.Empty || tag === ValueTag.Error)) {
            return undefined;
        }
        switch (tag) {
            case ValueTag.Number:
                return {
                    kind: 'value',
                    sheetName,
                    row,
                    col,
                    value: args.state.workbook.cellStore.numbers[cellIndex] ?? 0,
                    ...(explicitFormat === undefined ? {} : { explicitFormat }),
                };
            case ValueTag.Boolean:
                return {
                    kind: 'value',
                    sheetName,
                    row,
                    col,
                    value: (args.state.workbook.cellStore.numbers[cellIndex] ?? 0) !== 0,
                    ...(explicitFormat === undefined ? {} : { explicitFormat }),
                };
            case ValueTag.String: {
                const snapshot = args.getCellByIndex(cellIndex);
                if (explicitFormat === undefined) {
                    delete snapshot.format;
                    delete snapshot.numberFormatId;
                }
                else {
                    snapshot.format = explicitFormat;
                }
                return {
                    kind: 'snapshot',
                    sheetName,
                    row,
                    col,
                    snapshot,
                    ...(explicitFormat === undefined ? {} : { explicitFormat }),
                };
            }
            case ValueTag.Empty:
            case ValueTag.Error:
                return {
                    kind: 'blank',
                    sheetName,
                    row,
                    col,
                    restoreExplicitBlank: (args.state.workbook.cellStore.versions[cellIndex] ?? 0) !== 0 || (flags & 256 /* CellFlags.AuthoredBlank */) !== 0,
                    ...(explicitFormat === undefined ? {} : { explicitFormat }),
                };
        }
        return undefined;
    };
    const canDeferDeletedCellUndoRecordCapture = (cellIndex) => {
        const flags = args.state.workbook.cellStore.flags[cellIndex] ?? 0;
        if ((flags & (2 /* CellFlags.HasFormula */ | 4 /* CellFlags.JsOnly */ | 8 /* CellFlags.InCycle */ | 64 /* CellFlags.SpillChild */ | 128 /* CellFlags.PivotOutput */)) !== 0) {
            return false;
        }
        if (args.state.formulas.has(cellIndex)) {
            return false;
        }
        return args.state.workbook.getCellFormat(cellIndex) === undefined;
    };
    const captureDeletedCellUndoHandle = (cellIndex, sheetName, row, col) => {
        if (canDeferDeletedCellUndoRecordCapture(cellIndex)) {
            return {
                kind: 'lazy-cell',
                cellIndex,
                sheetName,
                row,
                col,
            };
        }
        return captureDeletedCellUndoRecord(cellIndex, sheetName, row, col);
    };
    const materializeDeletedCellUndoRecord = (capture) => capture.kind === 'lazy-cell' ? captureDeletedCellUndoRecord(capture.cellIndex, capture.sheetName, capture.row, capture.col) : capture;
    const captureDeletedCellUndoRecordsForStructuralUndo = (sheetName, axis, start, count) => {
        const sheet = args.state.workbook.getSheet(sheetName);
        if (!sheet) {
            return [];
        }
        const readCellPosition = createCellPositionReader();
        const axisIds = sheet.logicalAxisMap.snapshot(axis, start, count).map((entry) => entry.id);
        const records = [];
        sheet.logical.listResidentCellIndicesUnordered(axis, axisIds).forEach((cellIndex) => {
            const position = readCellPosition(cellIndex);
            if (!position || position.sheetId !== sheet.id) {
                return;
            }
            const axisIndex = axis === 'row' ? position.row : position.col;
            if (axisIndex < start || axisIndex >= start + count) {
                return;
            }
            const record = captureDeletedCellUndoHandle(cellIndex, sheetName, position.row, position.col);
            if (record) {
                records.push(record);
            }
        });
        return records.toSorted((left, right) => left.row - right.row || left.col - right.col);
    };
    const createLazyStructuralDeleteInverseRecord = (prefixOpsBeforeDeletedCells, deletedCellRecords, prefixOpsAfterDeletedCells, formulaRecords, potentialNewCells) => {
        const record = {
            kind: 'ops',
            get ops() {
                if (cachedOps === undefined) {
                    const materializedDeletedRecords = deletedCellRecords
                        .map(materializeDeletedCellUndoRecord)
                        .filter((deletedRecord) => deletedRecord !== undefined);
                    materializedDeletedRecords.sort((left, right) => left.row - right.row || left.col - right.col);
                    addEngineCounter(args.state.counters, 'structuralUndoCapturedCells', materializedDeletedRecords.length);
                    if (formulaRecords.length > 0) {
                        addEngineCounter(args.state.counters, 'structuralUndoCapturedFormulas', formulaRecords.length);
                    }
                    cachedOps = [
                        ...prefixOpsBeforeDeletedCells,
                        ...materializedDeletedRecords.flatMap((deletedRecord) => structuralDeletedCellUndoRecordToOps(deletedRecord, args.toCellStateOps)),
                        ...prefixOpsAfterDeletedCells,
                        ...formulaRecords.map(structuralFormulaUndoCaptureToOp),
                    ];
                }
                return cachedOps;
            },
            potentialNewCells,
        };
        let cachedOps;
        return record;
    };
    const buildStructuralDeleteInverseRecord = (op, options = {}) => {
        const axis = op.kind === 'deleteRows' ? 'row' : 'column';
        const entries = axis === 'row'
            ? args.state.workbook.snapshotRowAxisEntries(op.sheetName, op.start, op.count)
            : args.state.workbook.snapshotColumnAxisEntries(op.sheetName, op.start, op.count);
        const transform = structuralTransformForOp(op);
        const prefixOpsBeforeDeletedCells = [
            ...clearStructuralSheetMetadataOps(args.state.workbook, op.sheetName, transform),
            axis === 'row'
                ? {
                    kind: 'insertRows',
                    sheetName: op.sheetName,
                    start: op.start,
                    count: op.count,
                    entries,
                }
                : {
                    kind: 'insertColumns',
                    sheetName: op.sheetName,
                    start: op.start,
                    count: op.count,
                    entries,
                },
            ...sheetMetadataToOps(args.state.workbook, op.sheetName, { includeAxisEntries: false }),
        ];
        const deletedCellRecords = captureDeletedCellUndoRecordsForStructuralUndo(op.sheetName, axis, op.start, op.count);
        const prefixOpsAfterDeletedCells = captureStructuralWorkbookMetadataOps(args.state.workbook);
        return createLazyStructuralDeleteInverseRecord(prefixOpsBeforeDeletedCells, deletedCellRecords, prefixOpsAfterDeletedCells, captureFormulaCellRecordsForStructuralUndo(op.sheetName, axis, op.start, op.count, {
            skipRedundantDirectAggregateCaptures: options.includeStandaloneFormulaUndoOps !== true,
        }), 1);
    };
    return {
        captureFormulaCellStateForStructuralUndo,
        captureFormulaCellStateForStructuralInsertUndo,
        buildStructuralDeleteInverseRecord,
    };
}
//# sourceMappingURL=mutation-structural-delete-inverse.js.map