import { parseCellAddress } from '@bilig/formula';
import { ValueTag } from '@bilig/protocol';
import { makeCellKey } from '../../workbook-store.js';
import { isWorkbookTableHeaderCell } from './operation-table-header-rename.js';
function isFastHistoryOp(op) {
    return (op.kind === 'upsertWorkbook' ||
        op.kind === 'upsertSheet' ||
        op.kind === 'renameSheet' ||
        op.kind === 'setCellValue' ||
        op.kind === 'setCellFormula' ||
        op.kind === 'clearCell' ||
        op.kind === 'setCellFormat');
}
function cloneFastHistoryForwardOp(op) {
    switch (op.kind) {
        case 'upsertWorkbook':
            return {
                kind: 'upsertWorkbook',
                name: op.name,
            };
        case 'upsertSheet':
            return {
                kind: 'upsertSheet',
                name: op.name,
                order: op.order,
            };
        case 'renameSheet':
            return {
                kind: 'renameSheet',
                oldName: op.oldName,
                newName: op.newName,
            };
        case 'setCellValue':
            return {
                kind: 'setCellValue',
                sheetName: op.sheetName,
                address: op.address,
                value: op.value,
            };
        case 'setCellFormula':
            return {
                kind: 'setCellFormula',
                sheetName: op.sheetName,
                address: op.address,
                formula: op.formula,
            };
        case 'clearCell':
            return {
                kind: 'clearCell',
                sheetName: op.sheetName,
                address: op.address,
            };
        case 'setCellFormat':
            return {
                kind: 'setCellFormat',
                sheetName: op.sheetName,
                address: op.address,
                format: op.format,
            };
    }
}
function cloneFastHistoryUndoOp(op) {
    if (op.kind === 'deleteSheet') {
        return {
            kind: 'deleteSheet',
            name: op.name,
        };
    }
    return cloneFastHistoryForwardOp(op);
}
function restoreCellOpFromSnapshot(workbook, getCellByIndex, sheetName, address, preparedCellAddress) {
    const sheet = workbook.getSheet(sheetName);
    const cellIndex = preparedCellAddress && sheet
        ? workbook.cellKeyToIndex.get(makeCellKey(sheet.id, preparedCellAddress.row, preparedCellAddress.col))
        : workbook.getCellIndex(sheetName, address);
    if (cellIndex === undefined) {
        return { kind: 'clearCell', sheetName, address };
    }
    const snapshot = getCellByIndex(cellIndex);
    if (snapshot.formula !== undefined) {
        return {
            kind: 'setCellFormula',
            sheetName,
            address,
            formula: snapshot.formula,
        };
    }
    switch (snapshot.value.tag) {
        case ValueTag.Empty:
        case ValueTag.Error:
            return (snapshot.flags & 256 /* CellFlags.AuthoredBlank */) !== 0
                ? { kind: 'setCellValue', sheetName, address, value: null }
                : { kind: 'clearCell', sheetName, address };
        case ValueTag.Number:
        case ValueTag.Boolean:
        case ValueTag.String:
            return {
                kind: 'setCellValue',
                sheetName,
                address,
                value: snapshot.value.value,
            };
    }
}
function buildFastInverseOp(workbook, getCellByIndex, op, preparedCellAddress) {
    switch (op.kind) {
        case 'upsertWorkbook':
            return {
                kind: 'upsertWorkbook',
                name: workbook.workbookName,
            };
        case 'upsertSheet': {
            const existing = workbook.getSheet(op.name);
            if (!existing) {
                return { kind: 'deleteSheet', name: op.name };
            }
            return {
                kind: 'upsertSheet',
                name: existing.name,
                order: existing.order,
            };
        }
        case 'renameSheet': {
            const existing = workbook.getSheet(op.newName);
            if (!existing) {
                return null;
            }
            return {
                kind: 'renameSheet',
                oldName: op.newName,
                newName: op.oldName,
            };
        }
        case 'setCellValue':
        case 'setCellFormula':
        case 'clearCell':
            if (isFastHistoryTableHeaderCell(workbook, op, preparedCellAddress)) {
                return null;
            }
            return restoreCellOpFromSnapshot(workbook, getCellByIndex, op.sheetName, op.address, preparedCellAddress);
        case 'setCellFormat': {
            const sheet = workbook.getSheet(op.sheetName);
            const cellIndex = preparedCellAddress && sheet
                ? workbook.cellKeyToIndex.get(makeCellKey(sheet.id, preparedCellAddress.row, preparedCellAddress.col))
                : workbook.getCellIndex(op.sheetName, op.address);
            return {
                kind: 'setCellFormat',
                sheetName: op.sheetName,
                address: op.address,
                format: cellIndex === undefined ? null : (workbook.getCellFormat(cellIndex) ?? null),
            };
        }
        default:
            return null;
    }
}
function isFastHistoryTableHeaderCell(workbook, op, preparedCellAddress) {
    const parsed = preparedCellAddress ?? parseCellAddress(op.address, op.sheetName);
    return isWorkbookTableHeaderCell(workbook, op.sheetName, parsed.row, parsed.col);
}
function collectCreatedSheetNames(workbook, ops) {
    const knownSheetNames = new Set(workbook.sheetsByName.keys());
    const liveCreatedSheetNames = new Set();
    const createdSheetNames = new Set();
    for (let index = 0; index < ops.length; index += 1) {
        const op = ops[index];
        if (!op) {
            continue;
        }
        if (op.kind === 'upsertSheet') {
            if (!knownSheetNames.has(op.name)) {
                liveCreatedSheetNames.add(op.name);
                createdSheetNames.add(op.name);
            }
            knownSheetNames.add(op.name);
            continue;
        }
        if (op.kind !== 'renameSheet') {
            continue;
        }
        if (liveCreatedSheetNames.delete(op.oldName)) {
            liveCreatedSheetNames.add(op.newName);
            createdSheetNames.add(op.newName);
        }
        if (knownSheetNames.delete(op.oldName)) {
            knownSheetNames.add(op.newName);
        }
    }
    return createdSheetNames;
}
export function tryBuildFastMutationHistory(args) {
    const forwardOps = Array(args.ops.length);
    if (args.preparedCellAddressesByOpIndex && args.preparedCellAddressesByOpIndex.length !== args.ops.length) {
        throw new Error('Prepared cell addresses must align with fast-history operations');
    }
    for (let index = 0; index < args.ops.length; index += 1) {
        const op = args.ops[index];
        if (op === undefined || !isFastHistoryOp(op)) {
            return null;
        }
        forwardOps[index] = args.cloneForwardOps === false ? op : cloneFastHistoryForwardOp(op);
    }
    const createdSheetNames = collectCreatedSheetNames(args.workbook, forwardOps);
    const inverseOps = [];
    for (let index = forwardOps.length - 1; index >= 0; index -= 1) {
        const op = forwardOps[index];
        if (op === undefined) {
            return null;
        }
        if ((op.kind === 'setCellValue' || op.kind === 'setCellFormula' || op.kind === 'clearCell' || op.kind === 'setCellFormat') &&
            createdSheetNames.has(op.sheetName)) {
            continue;
        }
        const inverse = buildFastInverseOp(args.workbook, args.getCellByIndex, op, args.preparedCellAddressesByOpIndex?.[index] ?? null);
        if (!inverse) {
            return null;
        }
        inverseOps.push(inverse);
    }
    return {
        forward: args.potentialNewCells === undefined
            ? { kind: 'ops', ops: forwardOps }
            : { kind: 'ops', ops: forwardOps, potentialNewCells: args.potentialNewCells },
        inverse: {
            kind: 'ops',
            ops: inverseOps,
            potentialNewCells: args.ops.length,
        },
        undoOps: args.includeUndoOps === false
            ? null
            : inverseOps.map((op) => {
                if (op.kind === 'deleteSheet') {
                    return cloneFastHistoryUndoOp(op);
                }
                if (!isFastHistoryOp(op)) {
                    throw new TypeError(`Unsupported fast-path undo op: ${op.kind}`);
                }
                return cloneFastHistoryUndoOp(op);
            }),
    };
}
//# sourceMappingURL=mutation-history-fast-path.js.map