import { Effect } from 'effect';
import { ValueTag } from '@bilig/protocol';
import { formatAddress, parseCellAddress, translateFormulaReferences } from '@bilig/formula';
import { normalizeRange } from '../../engine-range-utils.js';
import { WorkbookStore } from '../../workbook-store.js';
import { EngineCellStateError } from '../errors.js';
function cellStateErrorMessage(message, cause) {
    return cause instanceof Error && cause.message.length > 0 ? cause.message : message;
}
function translateFormulaForTarget(formula, sourceSheetName, sourceAddress, targetSheetName, targetAddress) {
    const source = parseCellAddress(sourceAddress, sourceSheetName);
    const target = parseCellAddress(targetAddress, targetSheetName);
    return translateFormulaReferences(formula, target.row - source.row, target.col - source.col);
}
export function createEngineCellStateService(args) {
    const getStoredStyleId = (sheetName, address) => {
        const parsed = parseCellAddress(address, sheetName);
        return args.state.workbook.getStyleId(sheetName, parsed.row, parsed.col);
    };
    const toCellStateOpsNow = (sheetName, address, snapshot, sourceSheetName, sourceAddress, formatOverride = snapshot.format ?? null, styleIdOverride = snapshot.styleId ?? WorkbookStore.defaultStyleId, options = { clearExistingFormat: true }) => {
        const ops = [];
        const targetCellIndex = args.state.workbook.getCellIndex(sheetName, address);
        const explicitCurrentFormat = targetCellIndex === undefined ? undefined : args.state.workbook.getCellFormat(targetCellIndex);
        const parsedTarget = parseCellAddress(address, sheetName);
        const currentRangeFormatId = args.state.workbook.getRangeFormatId(sheetName, parsedTarget.row, parsedTarget.col);
        const currentRangeFormat = currentRangeFormatId === WorkbookStore.defaultFormatId
            ? null
            : (args.state.workbook.getCellNumberFormat(currentRangeFormatId)?.code ?? null);
        const currentFormat = explicitCurrentFormat ?? currentRangeFormat;
        const currentStyleId = args.state.workbook.getStyleId(sheetName, parsedTarget.row, parsedTarget.col);
        const nextFormat = formatOverride === '' ? null : formatOverride;
        const isAuthoredBlank = (snapshot.flags & 256 /* CellFlags.AuthoredBlank */) !== 0;
        const explicitBlankOp = { kind: 'setCellValue', sheetName, address, value: null };
        const shouldRestoreExplicitBlank = (snapshot.version ?? 0) !== 0 || isAuthoredBlank;
        if (snapshot.formula !== undefined) {
            const translatedFormula = sourceSheetName && sourceAddress
                ? translateFormulaForTarget(snapshot.formula, sourceSheetName, sourceAddress, sheetName, address)
                : snapshot.formula;
            ops.push({ kind: 'setCellFormula', sheetName, address, formula: translatedFormula });
        }
        else {
            switch (snapshot.value.tag) {
                case ValueTag.Empty:
                    ops.push(shouldRestoreExplicitBlank ? explicitBlankOp : { kind: 'clearCell', sheetName, address });
                    break;
                case ValueTag.Number:
                case ValueTag.Boolean:
                case ValueTag.String:
                    ops.push({ kind: 'setCellValue', sheetName, address, value: snapshot.value.value });
                    break;
                case ValueTag.Error:
                    ops.push(shouldRestoreExplicitBlank ? explicitBlankOp : { kind: 'clearCell', sheetName, address });
                    break;
            }
        }
        if (nextFormat !== null && (options.forceFormatWrite === true || nextFormat !== currentFormat)) {
            ops.push({
                kind: 'setCellFormat',
                sheetName,
                address,
                format: nextFormat,
            });
        }
        else if (nextFormat === null && options.clearExistingFormat === true) {
            if (explicitCurrentFormat !== undefined) {
                ops.push({
                    kind: 'setCellFormat',
                    sheetName,
                    address,
                    format: nextFormat,
                });
            }
            if (currentRangeFormatId !== WorkbookStore.defaultFormatId) {
                ops.push({
                    kind: 'setFormatRange',
                    range: {
                        sheetName,
                        startAddress: address,
                        endAddress: address,
                    },
                    formatId: WorkbookStore.defaultFormatId,
                });
            }
        }
        if (styleIdOverride !== currentStyleId) {
            ops.push({
                kind: 'setStyleRange',
                range: {
                    sheetName,
                    startAddress: address,
                    endAddress: address,
                },
                styleId: styleIdOverride,
            });
        }
        return ops;
    };
    const captureStoredCellOpsNow = (cellIndex, sheetName, address, sourceSheetName, sourceAddress) => toCellStateOpsNow(sheetName, address, args.getCellByIndex(cellIndex), sourceSheetName, sourceAddress, args.state.workbook.getCellFormat(cellIndex) ?? null, getStoredStyleId(sheetName, address), { forceFormatWrite: true });
    const restoreCellOpsNow = (sheetName, address) => {
        const cellIndex = args.state.workbook.getCellIndex(sheetName, address);
        if (cellIndex === undefined) {
            return [{ kind: 'clearCell', sheetName, address }];
        }
        const snapshot = args.getCellByIndex(cellIndex);
        const explicitFormat = args.state.workbook.getCellFormat(cellIndex) ?? null;
        const explicitStyleId = getStoredStyleId(sheetName, address);
        const restoreOptions = explicitFormat === null ? { clearExistingFormat: false } : undefined;
        if (snapshot.formula !== undefined) {
            return toCellStateOpsNow(sheetName, address, snapshot, undefined, undefined, explicitFormat, explicitStyleId, restoreOptions);
        }
        switch (snapshot.value.tag) {
            case ValueTag.Empty:
            case ValueTag.Error:
                return toCellStateOpsNow(sheetName, address, snapshot, undefined, undefined, explicitFormat, explicitStyleId, restoreOptions);
            case ValueTag.Number:
            case ValueTag.Boolean:
            case ValueTag.String:
                return toCellStateOpsNow(sheetName, address, snapshot, undefined, undefined, explicitFormat, explicitStyleId, restoreOptions);
        }
    };
    const readRangeCellsNow = (range) => {
        const bounds = normalizeRange(range);
        const rows = [];
        for (let row = bounds.startRow; row <= bounds.endRow; row += 1) {
            const cells = [];
            for (let col = bounds.startCol; col <= bounds.endCol; col += 1) {
                cells.push(args.getCell(range.sheetName, formatAddress(row, col)));
            }
            rows.push(cells);
        }
        return rows;
    };
    return {
        captureStoredCellOps(cellIndex, sheetName, address, sourceSheetName, sourceAddress) {
            return Effect.try({
                try: () => captureStoredCellOpsNow(cellIndex, sheetName, address, sourceSheetName, sourceAddress),
                catch: (cause) => new EngineCellStateError({
                    message: cellStateErrorMessage(`Failed to capture stored cell ops for ${sheetName}!${address}`, cause),
                    cause,
                }),
            });
        },
        restoreCellOps(sheetName, address) {
            return Effect.try({
                try: () => restoreCellOpsNow(sheetName, address),
                catch: (cause) => new EngineCellStateError({
                    message: cellStateErrorMessage(`Failed to restore cell ops for ${sheetName}!${address}`, cause),
                    cause,
                }),
            });
        },
        readRangeCells(range) {
            return Effect.try({
                try: () => readRangeCellsNow(range),
                catch: (cause) => new EngineCellStateError({
                    message: cellStateErrorMessage(`Failed to read range ${range.sheetName}!${range.startAddress}:${range.endAddress}`, cause),
                    cause,
                }),
            });
        },
        toCellStateOps(sheetName, address, snapshot, sourceSheetName, sourceAddress, formatOverride, styleIdOverride) {
            return Effect.try({
                try: () => toCellStateOpsNow(sheetName, address, snapshot, sourceSheetName, sourceAddress, formatOverride, styleIdOverride),
                catch: (cause) => new EngineCellStateError({
                    message: cellStateErrorMessage(`Failed to materialize cell state ops for ${sheetName}!${address}`, cause),
                    cause,
                }),
            });
        },
        captureStoredCellOpsNow,
        restoreCellOpsNow,
        readRangeCellsNow,
        toCellStateOpsNow,
    };
}
//# sourceMappingURL=cell-state-service.js.map