import { parseCellAddress } from '@bilig/formula';
import { structuralTransformForOp } from '../../engine-structural-utils.js';
import { sheetMetadataToOps } from '../../engine-snapshot-utils.js';
import { buildMutationMetadataInverseOps } from './mutation-inverse-metadata-ops.js';
import { captureStructuralWorkbookMetadataOps, clearStructuralSheetMetadataOps } from './mutation-structural-metadata-ops.js';
import { findTableHeaderCell } from './operation-table-header-rename.js';
const mutationCoreCapturedInverseKinds = new Set([
    'deleteSheet',
    'deleteRows',
    'deleteColumns',
    'setCellValue',
    'setCellFormula',
    'clearCell',
]);
function isMutationCoreCapturedInverseOp(op) {
    return mutationCoreCapturedInverseKinds.has(op.kind);
}
function restoreCellOpsWithTableHeaderRename(args) {
    const parsed = parseCellAddress(args.op.address, args.op.sheetName);
    const header = findTableHeaderCell(args.workbook.listTables(), args.op.sheetName, parsed.row, parsed.col);
    if (!header) {
        return [...args.restoreOps];
    }
    const formulaRestoreIndex = args.restoreOps.findIndex((restoreOp) => restoreOp.kind === 'setCellFormula' && restoreOp.sheetName === args.op.sheetName && restoreOp.address === args.op.address);
    if (formulaRestoreIndex < 0) {
        if (args.op.kind === 'setCellFormula') {
            const cellRestoreIndex = args.restoreOps.findIndex((restoreOp) => (restoreOp.kind === 'setCellValue' || restoreOp.kind === 'clearCell') &&
                restoreOp.sheetName === args.op.sheetName &&
                restoreOp.address === args.op.address);
            if (cellRestoreIndex >= 0) {
                // Formula writes do not rename table headers, but literal/empty restores can.
                const tableRestoreOp = { kind: 'upsertTable', table: structuredClone(header.table) };
                return [...args.restoreOps.slice(0, cellRestoreIndex + 1), tableRestoreOp, ...args.restoreOps.slice(cellRestoreIndex + 1)];
            }
        }
        return [...args.restoreOps];
    }
    const headerName = header.table.columnNames[header.columnIndex] ?? '';
    // Formula restores do not rename table headers, so replay the previous header label first.
    const headerRenameOp = { kind: 'setCellValue', sheetName: args.op.sheetName, address: args.op.address, value: headerName };
    return [...args.restoreOps.slice(0, formulaRestoreIndex), headerRenameOp, ...args.restoreOps.slice(formulaRestoreIndex)];
}
function captureDeletedSheetInverseOps(workbook, sheetName, captureSheetCellState) {
    const sheet = workbook.getSheet(sheetName);
    if (!sheet) {
        return [];
    }
    const restoredOps = [{ kind: 'upsertSheet', name: sheet.name, order: sheet.order }];
    restoredOps.push(...sheetMetadataToOps(workbook, sheet.name));
    workbook
        .listTables()
        .filter((table) => table.sheetName === sheet.name)
        .forEach((table) => {
        restoredOps.push({
            kind: 'upsertTable',
            table: structuredClone(table),
        });
    });
    workbook
        .listSpills()
        .filter((spill) => spill.sheetName === sheet.name)
        .forEach((spill) => {
        restoredOps.push({
            kind: 'upsertSpillRange',
            sheetName: spill.sheetName,
            address: spill.address,
            rows: spill.rows,
            cols: spill.cols,
        });
    });
    workbook
        .listPivots()
        .filter((pivot) => pivot.sheetName === sheet.name && pivot.source)
        .forEach((pivot) => {
        if (!pivot.source) {
            return;
        }
        restoredOps.push({
            kind: 'upsertPivotTable',
            name: pivot.name,
            sheetName: pivot.sheetName,
            address: pivot.address,
            source: { ...pivot.source },
            groupBy: [...pivot.groupBy],
            values: pivot.values.map((value) => Object.assign({}, value)),
            rows: pivot.rows,
            cols: pivot.cols,
        });
    });
    workbook
        .listCharts()
        .filter((chart) => chart.sheetName === sheet.name || chart.source.sheetName === sheet.name)
        .forEach((chart) => {
        restoredOps.push({
            kind: 'upsertChart',
            chart: structuredClone(chart),
        });
    });
    workbook
        .listImages()
        .filter((image) => image.sheetName === sheet.name)
        .forEach((image) => {
        restoredOps.push({
            kind: 'upsertImage',
            image: structuredClone(image),
        });
    });
    workbook
        .listShapes()
        .filter((shape) => shape.sheetName === sheet.name)
        .forEach((shape) => {
        restoredOps.push({
            kind: 'upsertShape',
            shape: structuredClone(shape),
        });
    });
    restoredOps.push(...captureSheetCellState(sheet.name));
    return restoredOps;
}
export function createMutationCoreInverseOps(args) {
    const inverseOpsFor = (op) => {
        const metadataInverseOps = buildMutationMetadataInverseOps(args.workbook, op);
        if (metadataInverseOps !== undefined) {
            return metadataInverseOps;
        }
        if (!isMutationCoreCapturedInverseOp(op)) {
            throw new Error(`Unhandled inverse operation: ${op.kind}`);
        }
        switch (op.kind) {
            case 'deleteSheet':
                return captureDeletedSheetInverseOps(args.workbook, op.name, args.captureSheetCellState);
            case 'deleteRows': {
                const entries = args.workbook.snapshotRowAxisEntries(op.sheetName, op.start, op.count);
                const transform = structuralTransformForOp(op);
                return [
                    ...clearStructuralSheetMetadataOps(args.workbook, op.sheetName, transform),
                    {
                        kind: 'insertRows',
                        sheetName: op.sheetName,
                        start: op.start,
                        count: op.count,
                        entries,
                    },
                    ...sheetMetadataToOps(args.workbook, op.sheetName, { includeAxisEntries: false }),
                    ...args.captureRowRangeCellState(op.sheetName, op.start, op.count),
                    ...args.captureFormulaCellStateForStructuralUndo(op.sheetName, 'row', op.start, op.count),
                    ...captureStructuralWorkbookMetadataOps(args.workbook),
                ];
            }
            case 'deleteColumns': {
                const entries = args.workbook.snapshotColumnAxisEntries(op.sheetName, op.start, op.count);
                const transform = structuralTransformForOp(op);
                return [
                    ...clearStructuralSheetMetadataOps(args.workbook, op.sheetName, transform),
                    {
                        kind: 'insertColumns',
                        sheetName: op.sheetName,
                        start: op.start,
                        count: op.count,
                        entries,
                    },
                    ...sheetMetadataToOps(args.workbook, op.sheetName, { includeAxisEntries: false }),
                    ...args.captureColumnRangeCellState(op.sheetName, op.start, op.count),
                    ...args.captureFormulaCellStateForStructuralUndo(op.sheetName, 'column', op.start, op.count),
                    ...captureStructuralWorkbookMetadataOps(args.workbook),
                ];
            }
            case 'setCellValue':
            case 'setCellFormula':
            case 'clearCell':
                return restoreCellOpsWithTableHeaderRename({
                    workbook: args.workbook,
                    op,
                    restoreOps: args.restoreCellOps(op.sheetName, op.address),
                });
            default: {
                const exhaustive = op;
                return exhaustive;
            }
        }
    };
    const buildInverseOps = (ops) => {
        const inverseOps = [];
        for (let index = ops.length - 1; index >= 0; index -= 1) {
            const op = ops[index];
            if (op !== undefined) {
                inverseOps.push(...inverseOpsFor(op));
            }
        }
        return inverseOps;
    };
    return { inverseOpsFor, buildInverseOps };
}
//# sourceMappingURL=mutation-core-inverse-ops.js.map