import { Effect } from 'effect';
import { formatAddress } from '@bilig/formula';
import { ValueTag } from '@bilig/protocol';
import { parseCsv, parseCsvCellInput, resolveCsvParseOptions } from '../../csv.js';
import { normalizeRange } from '../../engine-range-utils.js';
import { WorkbookStore } from '../../workbook-store.js';
import { EngineMutationError } from '../errors.js';
import { findTableHeaderCell } from './operation-table-header-rename.js';
import { excelCompatibleTableColumnName } from './table-column-name-helpers.js';
import { getMutationMatrixCell } from './mutation-cell-content-helpers.js';
import { hasMutationCellContent, hasStoredMutationCellState, readDesiredMutationCellState, readStoredMutationCellState, shouldApplyMutationCellState, } from './mutation-cell-state-helpers.js';
function mutationRangeErrorMessage(message, cause) {
    return cause instanceof Error && cause.message.length > 0 ? cause.message : message;
}
function emptyCellSnapshot(sheetName, address) {
    return {
        sheetName,
        address,
        value: { tag: ValueTag.Empty },
        flags: 0,
        version: 0,
    };
}
export function createMutationRangeOperations(args) {
    const readStoredCellState = (sheetName, address) => {
        const bounds = normalizeRange({ sheetName, startAddress: address, endAddress: address });
        const styleId = args.workbook.getStyleId(sheetName, bounds.startRow, bounds.startCol);
        const cellIndex = args.workbook.getCellIndex(sheetName, address);
        const format = readEffectiveFormat(sheetName, address, cellIndex);
        if (cellIndex === undefined) {
            return { value: null, format, authoredBlank: false, styleId };
        }
        const snapshot = args.getCellByIndex(cellIndex);
        return readStoredMutationCellState(snapshot, format, args.workbook.cellStore.flags[cellIndex], styleId);
    };
    const readEffectiveFormat = (sheetName, address, cellIndex) => {
        if (cellIndex !== undefined) {
            const explicitFormat = args.workbook.getCellFormat(cellIndex);
            if (explicitFormat !== undefined) {
                return explicitFormat;
            }
        }
        const bounds = normalizeRange({ sheetName, startAddress: address, endAddress: address });
        const formatId = args.workbook.getRangeFormatId(sheetName, bounds.startRow, bounds.startCol);
        if (formatId === WorkbookStore.defaultFormatId) {
            return null;
        }
        return args.workbook.getCellNumberFormat(formatId)?.code ?? null;
    };
    const generatedTableHeaderNameForBlankState = (targetSheetName, targetAddress, snapshot) => {
        if (snapshot.formula !== undefined || (snapshot.value.tag !== ValueTag.Empty && snapshot.value.tag !== ValueTag.Error)) {
            return undefined;
        }
        const bounds = normalizeRange({ sheetName: targetSheetName, startAddress: targetAddress, endAddress: targetAddress });
        const header = findTableHeaderCell(args.workbook.listTables(), targetSheetName, bounds.startRow, bounds.startCol);
        if (!header) {
            return undefined;
        }
        return excelCompatibleTableColumnName('', header.table.columnNames, header.columnIndex);
    };
    const readDesiredCellState = (targetSheetName, targetAddress, snapshot, sourceSheetName, sourceAddress, formatOverride = snapshot.format ?? null, styleIdOverride = snapshot.styleId ?? WorkbookStore.defaultStyleId) => {
        const desired = readDesiredMutationCellState({
            targetSheetName,
            targetAddress,
            snapshot,
            ...(sourceSheetName !== undefined ? { sourceSheetName } : {}),
            ...(sourceAddress !== undefined ? { sourceAddress } : {}),
            formatOverride,
            styleIdOverride,
        });
        const generatedHeaderName = generatedTableHeaderNameForBlankState(targetSheetName, targetAddress, snapshot);
        if (generatedHeaderName === undefined || desired.formula !== undefined) {
            return desired;
        }
        return { ...desired, value: generatedHeaderName, authoredBlank: false };
    };
    const hasStoredCellState = (sheetName, address) => {
        const bounds = normalizeRange({ sheetName, startAddress: address, endAddress: address });
        const styleId = args.workbook.getStyleId(sheetName, bounds.startRow, bounds.startCol);
        const cellIndex = args.workbook.getCellIndex(sheetName, address);
        if (cellIndex === undefined) {
            return styleId !== WorkbookStore.defaultStyleId;
        }
        return (hasStoredMutationCellState(args.getCellByIndex(cellIndex), args.workbook.getCellFormat(cellIndex), args.workbook.cellStore.flags[cellIndex]) || styleId !== WorkbookStore.defaultStyleId);
    };
    const shouldApplyCellState = (targetSheetName, targetAddress, snapshot, sourceSheetName, sourceAddress) => {
        const stored = readStoredCellState(targetSheetName, targetAddress);
        const desired = readDesiredCellState(targetSheetName, targetAddress, snapshot, sourceSheetName, sourceAddress);
        return shouldApplyMutationCellState(stored, desired);
    };
    return {
        setRangeValues(range, values) {
            return Effect.try({
                try: () => {
                    const bounds = normalizeRange(range);
                    const expectedHeight = bounds.endRow - bounds.startRow + 1;
                    const expectedWidth = bounds.endCol - bounds.startCol + 1;
                    if (values.length !== expectedHeight || values.some((row) => row.length !== expectedWidth)) {
                        throw new Error('setRangeValues requires a value matrix that exactly matches the target range');
                    }
                    const ops = [];
                    for (let rowOffset = 0; rowOffset < expectedHeight; rowOffset += 1) {
                        for (let colOffset = 0; colOffset < expectedWidth; colOffset += 1) {
                            const address = formatAddress(bounds.startRow + rowOffset, bounds.startCol + colOffset);
                            const current = readStoredCellState(range.sheetName, address);
                            const nextValue = values[rowOffset][colOffset] ?? null;
                            if (current.formula === undefined && current.value === nextValue) {
                                continue;
                            }
                            ops.push({
                                kind: 'setCellValue',
                                sheetName: range.sheetName,
                                address,
                                value: nextValue,
                            });
                        }
                    }
                    if (ops.length === 0) {
                        return;
                    }
                    Effect.runSync(args.executeLocal(ops, ops.length));
                },
                catch: (cause) => new EngineMutationError({
                    message: mutationRangeErrorMessage('Failed to set range values', cause),
                    cause,
                }),
            });
        },
        setRangeFormulas(range, formulas) {
            return Effect.try({
                try: () => {
                    const bounds = normalizeRange(range);
                    const expectedHeight = bounds.endRow - bounds.startRow + 1;
                    const expectedWidth = bounds.endCol - bounds.startCol + 1;
                    if (formulas.length !== expectedHeight || formulas.some((row) => row.length !== expectedWidth)) {
                        throw new Error('setRangeFormulas requires a formula matrix that exactly matches the target range');
                    }
                    const opCount = expectedHeight * expectedWidth;
                    const ops = Array.from({ length: opCount });
                    let opIndex = 0;
                    for (let rowOffset = 0; rowOffset < expectedHeight; rowOffset += 1) {
                        for (let colOffset = 0; colOffset < expectedWidth; colOffset += 1) {
                            ops[opIndex] = {
                                kind: 'setCellFormula',
                                sheetName: range.sheetName,
                                address: formatAddress(bounds.startRow + rowOffset, bounds.startCol + colOffset),
                                formula: formulas[rowOffset][colOffset] ?? '',
                            };
                            opIndex += 1;
                        }
                    }
                    Effect.runSync(args.executeLocal(ops, opCount));
                },
                catch: (cause) => new EngineMutationError({
                    message: mutationRangeErrorMessage('Failed to set range formulas', cause),
                    cause,
                }),
            });
        },
        clearRange(range) {
            return Effect.try({
                try: () => {
                    const bounds = normalizeRange(range);
                    const ops = [];
                    const sheet = args.workbook.getSheet(range.sheetName);
                    if (!sheet) {
                        return;
                    }
                    sheet.grid.forEachCellEntry((cellIndex, row, col) => {
                        if (row < bounds.startRow || row > bounds.endRow || col < bounds.startCol || col > bounds.endCol) {
                            return;
                        }
                        if (!hasMutationCellContent(args.getCellByIndex(cellIndex))) {
                            return;
                        }
                        ops.push({
                            kind: 'clearCell',
                            sheetName: range.sheetName,
                            address: formatAddress(row, col),
                        });
                    });
                    if (ops.length === 0) {
                        return;
                    }
                    Effect.runSync(args.executeLocal(ops, ops.length));
                },
                catch: (cause) => new EngineMutationError({
                    message: mutationRangeErrorMessage('Failed to clear range', cause),
                    cause,
                }),
            });
        },
        fillRange(source, target) {
            return Effect.try({
                try: () => {
                    const sourceMatrix = args.readRangeCells(source);
                    const targetBounds = normalizeRange(target);
                    const sourceBounds = normalizeRange(source);
                    const sourceHeight = sourceMatrix.length;
                    const sourceWidth = sourceMatrix[0]?.length ?? 0;
                    if (sourceHeight === 0 || sourceWidth === 0) {
                        return;
                    }
                    const ops = [];
                    for (let row = targetBounds.startRow; row <= targetBounds.endRow; row += 1) {
                        for (let col = targetBounds.startCol; col <= targetBounds.endCol; col += 1) {
                            const sourceRowOffset = (row - targetBounds.startRow) % sourceHeight;
                            const sourceColOffset = (col - targetBounds.startCol) % sourceWidth;
                            const sourceCell = getMutationMatrixCell(sourceMatrix, sourceRowOffset, sourceColOffset);
                            const sourceAddress = formatAddress(sourceBounds.startRow + sourceRowOffset, sourceBounds.startCol + sourceColOffset);
                            const nextAddress = formatAddress(row, col);
                            if (!shouldApplyCellState(target.sheetName, nextAddress, sourceCell, source.sheetName, sourceAddress)) {
                                continue;
                            }
                            ops.push(...args.toCellStateOps(target.sheetName, nextAddress, sourceCell, source.sheetName, sourceAddress));
                        }
                    }
                    if (ops.length === 0) {
                        return;
                    }
                    Effect.runSync(args.executeLocal(ops, ops.length));
                },
                catch: (cause) => new EngineMutationError({
                    message: mutationRangeErrorMessage('Failed to fill range', cause),
                    cause,
                }),
            });
        },
        copyRange(source, target) {
            return Effect.try({
                try: () => {
                    const sourceMatrix = args.readRangeCells(source);
                    const targetBounds = normalizeRange(target);
                    const sourceBounds = normalizeRange(source);
                    const sourceHeight = sourceBounds.endRow - sourceBounds.startRow + 1;
                    const sourceWidth = sourceBounds.endCol - sourceBounds.startCol + 1;
                    const targetHeight = targetBounds.endRow - targetBounds.startRow + 1;
                    const targetWidth = targetBounds.endCol - targetBounds.startCol + 1;
                    if (sourceHeight !== targetHeight || sourceWidth !== targetWidth) {
                        throw new Error('copyRange requires source and target dimensions to match exactly');
                    }
                    const ops = [];
                    for (let rowOffset = 0; rowOffset < targetHeight; rowOffset += 1) {
                        for (let colOffset = 0; colOffset < targetWidth; colOffset += 1) {
                            const nextAddress = formatAddress(targetBounds.startRow + rowOffset, targetBounds.startCol + colOffset);
                            const sourceAddress = formatAddress(sourceBounds.startRow + rowOffset, sourceBounds.startCol + colOffset);
                            const sourceCell = getMutationMatrixCell(sourceMatrix, rowOffset, colOffset);
                            if (!shouldApplyCellState(target.sheetName, nextAddress, sourceCell, source.sheetName, sourceAddress)) {
                                continue;
                            }
                            ops.push(...args.toCellStateOps(target.sheetName, nextAddress, sourceCell, source.sheetName, sourceAddress));
                        }
                    }
                    if (ops.length === 0) {
                        return;
                    }
                    Effect.runSync(args.executeLocal(ops, ops.length));
                },
                catch: (cause) => new EngineMutationError({
                    message: mutationRangeErrorMessage('Failed to copy range', cause),
                    cause,
                }),
            });
        },
        moveRange(source, target) {
            return Effect.try({
                try: () => {
                    const sourceMatrix = args.readRangeCells(source);
                    const targetBounds = normalizeRange(target);
                    const sourceBounds = normalizeRange(source);
                    const sourceHeight = sourceBounds.endRow - sourceBounds.startRow + 1;
                    const sourceWidth = sourceBounds.endCol - sourceBounds.startCol + 1;
                    const targetHeight = targetBounds.endRow - targetBounds.startRow + 1;
                    const targetWidth = targetBounds.endCol - targetBounds.startCol + 1;
                    if (sourceHeight !== targetHeight || sourceWidth !== targetWidth) {
                        throw new Error('moveRange requires source and target dimensions to match exactly');
                    }
                    if (source.sheetName === target.sheetName &&
                        sourceBounds.startRow === targetBounds.startRow &&
                        sourceBounds.endRow === targetBounds.endRow &&
                        sourceBounds.startCol === targetBounds.startCol &&
                        sourceBounds.endCol === targetBounds.endCol) {
                        return;
                    }
                    const ops = [];
                    for (let row = sourceBounds.startRow; row <= sourceBounds.endRow; row += 1) {
                        for (let col = sourceBounds.startCol; col <= sourceBounds.endCol; col += 1) {
                            const address = formatAddress(row, col);
                            if (!hasStoredCellState(source.sheetName, address)) {
                                continue;
                            }
                            ops.push(...args.toCellStateOps(source.sheetName, address, emptyCellSnapshot(source.sheetName, address)));
                        }
                    }
                    for (let rowOffset = 0; rowOffset < targetHeight; rowOffset += 1) {
                        for (let colOffset = 0; colOffset < targetWidth; colOffset += 1) {
                            const nextAddress = formatAddress(targetBounds.startRow + rowOffset, targetBounds.startCol + colOffset);
                            const sourceAddress = formatAddress(sourceBounds.startRow + rowOffset, sourceBounds.startCol + colOffset);
                            const sourceCell = getMutationMatrixCell(sourceMatrix, rowOffset, colOffset);
                            if (!shouldApplyCellState(target.sheetName, nextAddress, sourceCell, source.sheetName, sourceAddress)) {
                                continue;
                            }
                            ops.push(...args.toCellStateOps(target.sheetName, nextAddress, sourceCell, source.sheetName, sourceAddress));
                        }
                    }
                    if (ops.length === 0) {
                        return;
                    }
                    Effect.runSync(args.executeLocal(ops, ops.length));
                },
                catch: (cause) => new EngineMutationError({
                    message: mutationRangeErrorMessage('Failed to move range', cause),
                    cause,
                }),
            });
        },
        importSheetCsv(sheetName, csv, options) {
            return Effect.try({
                try: () => {
                    const csvOptions = resolveCsvParseOptions(csv, options);
                    const rows = parseCsv(csv, csvOptions);
                    const existingSheet = args.workbook.getSheet(sheetName);
                    const order = existingSheet?.order ?? args.workbook.sheetsByName.size;
                    const ops = [];
                    let potentialNewCells = 0;
                    if (existingSheet) {
                        ops.push({ kind: 'deleteSheet', name: sheetName });
                    }
                    ops.push({ kind: 'upsertSheet', name: sheetName, order });
                    rows.forEach((row, rowIndex) => {
                        row.forEach((raw, colIndex) => {
                            const parsed = parseCsvCellInput(raw, csvOptions);
                            if (!parsed) {
                                return;
                            }
                            const address = formatAddress(rowIndex, colIndex);
                            if (parsed.formula !== undefined) {
                                ops.push({ kind: 'setCellFormula', sheetName, address, formula: parsed.formula });
                                potentialNewCells += 1;
                                return;
                            }
                            ops.push({ kind: 'setCellValue', sheetName, address, value: parsed.value ?? null });
                            potentialNewCells += 1;
                        });
                    });
                    Effect.runSync(args.executeLocal(ops, potentialNewCells));
                },
                catch: (cause) => new EngineMutationError({
                    message: mutationRangeErrorMessage('Failed to import sheet CSV', cause),
                    cause,
                }),
            });
        },
    };
}
//# sourceMappingURL=mutation-range-operations.js.map