import { createCellNumberFormatRecord, } from '@bilig/protocol';
import { formatAddress, parseCellAddress } from '@bilig/formula';
import { intersectRangeBounds, normalizeRange } from './engine-range-utils.js';
import { applyStylePatch, clearStyleFields, cloneCellStyleRecord, normalizeCellStylePatch } from './engine-style-utils.js';
import { WorkbookStore } from './workbook-store.js';
export function buildStylePatchOps(workbook, range, patch) {
    const normalizedPatch = normalizeCellStylePatch(patch);
    if (!normalizedPatch.fill && !normalizedPatch.font && !normalizedPatch.alignment && !normalizedPatch.borders) {
        return [];
    }
    return materializeStyleRangeOps(workbook, range, (baseStyle) => workbook.internCellStyle(applyStylePatch(baseStyle, normalizedPatch)), stylePatchNeedsProofRecord(normalizedPatch));
}
export function buildStyleClearOps(workbook, range, fields) {
    return materializeStyleRangeOps(workbook, range, (baseStyle) => workbook.internCellStyle(clearStyleFields(baseStyle, fields)));
}
export function restoreStyleRangeOps(workbook, range) {
    const restoredTiles = coalesceStyleTiles(resolveOverlappingStyleTiles(workbook, range));
    const defaultTiles = resolveStyleTiles(workbook, range).filter((tile) => tile.styleId === WorkbookStore.defaultStyleId);
    const seenStyleIds = new Set();
    const ops = [];
    restoredTiles.forEach((tile) => {
        if (tile.styleId !== WorkbookStore.defaultStyleId && !seenStyleIds.has(tile.styleId)) {
            const style = workbook.getCellStyle(tile.styleId);
            if (style) {
                ops.push({
                    kind: 'upsertCellStyle',
                    style: cloneCellStyleRecord(style),
                });
            }
            seenStyleIds.add(tile.styleId);
        }
        ops.push({
            kind: 'setStyleRange',
            range: tile.range,
            styleId: tile.styleId,
        });
    });
    defaultTiles.forEach((tile) => {
        ops.push({
            kind: 'setStyleRange',
            range: tile.range,
            styleId: tile.styleId,
        });
    });
    return ops;
}
export function buildFormatPatchOps(workbook, range, format) {
    const normalized = workbook.internCellNumberFormat(typeof format === 'string' ? format : createCellNumberFormatRecord(WorkbookStore.defaultFormatId, format));
    return materializeFormatRangeOps(workbook, range, () => normalized.id, normalized);
}
export function buildFormatClearOps(workbook, range) {
    return materializeFormatRangeOps(workbook, range, () => WorkbookStore.defaultFormatId, undefined, true);
}
export function restoreFormatRangeOps(workbook, range) {
    const restoredTiles = resolveOverlappingFormatTiles(workbook, range);
    const defaultTiles = resolveFormatTiles(workbook, range).filter((tile) => tile.formatId === WorkbookStore.defaultFormatId);
    const seenFormatIds = new Set();
    const ops = [];
    restoredTiles.forEach((tile) => {
        if (tile.formatId !== WorkbookStore.defaultFormatId && !seenFormatIds.has(tile.formatId)) {
            const format = workbook.getCellNumberFormat(tile.formatId);
            if (format) {
                ops.push({
                    kind: 'upsertCellNumberFormat',
                    format: { ...format },
                });
            }
            seenFormatIds.add(tile.formatId);
        }
        ops.push({
            kind: 'setFormatRange',
            range: tile.range,
            formatId: tile.formatId,
        });
    });
    defaultTiles.forEach((tile) => {
        ops.push({
            kind: 'setFormatRange',
            range: tile.range,
            formatId: tile.formatId,
        });
    });
    return ops;
}
function materializeStyleRangeOps(workbook, range, resolveStyle, includeDefaultStyleRecord = false) {
    const tiles = resolveStyleTiles(workbook, range);
    const ops = [];
    let needsDefaultStyleRecord = false;
    tiles.forEach((tile) => {
        const current = workbook.getCellStyle(tile.styleId) ?? {
            id: WorkbookStore.defaultStyleId,
        };
        const next = resolveStyle(current, tile.styleId);
        const normalizedId = next.id || WorkbookStore.defaultStyleId;
        if (normalizedId === tile.styleId) {
            return;
        }
        if (normalizedId !== WorkbookStore.defaultStyleId) {
            ops.push({
                kind: 'upsertCellStyle',
                style: cloneCellStyleRecord(next),
            });
        }
        else if (includeDefaultStyleRecord) {
            needsDefaultStyleRecord = true;
        }
        ops.push({
            kind: 'setStyleRange',
            range: tile.range,
            styleId: normalizedId,
        });
    });
    if (needsDefaultStyleRecord) {
        return [
            {
                kind: 'upsertCellStyle',
                style: cloneCellStyleRecord(workbook.getCellStyle(WorkbookStore.defaultStyleId) ?? { id: WorkbookStore.defaultStyleId }),
            },
            ...ops,
        ];
    }
    return ops;
}
function materializeFormatRangeOps(workbook, range, resolveFormatId, upsertFormat, includeDefaultFormatRecord = false) {
    const tiles = resolveFormatTiles(workbook, range);
    const rangeOps = [];
    tiles.forEach((tile) => {
        const nextFormatId = resolveFormatId(tile.formatId, tile);
        if (nextFormatId === tile.formatId) {
            return;
        }
        rangeOps.push({
            kind: 'setFormatRange',
            range: tile.range,
            formatId: nextFormatId,
        });
    });
    if (rangeOps.length === 0) {
        return [];
    }
    if (upsertFormat && upsertFormat.id !== WorkbookStore.defaultFormatId) {
        return [{ kind: 'upsertCellNumberFormat', format: { ...upsertFormat } }, ...rangeOps];
    }
    if (includeDefaultFormatRecord) {
        return [
            {
                kind: 'upsertCellNumberFormat',
                format: {
                    ...(workbook.getCellNumberFormat(WorkbookStore.defaultFormatId) ?? {
                        id: WorkbookStore.defaultFormatId,
                        code: 'general',
                        kind: 'general',
                    }),
                },
            },
            ...rangeOps,
        ];
    }
    return rangeOps;
}
function sectionPatchNeedsProofRecord(value, keys) {
    if (value === undefined) {
        return false;
    }
    if (value === null) {
        return true;
    }
    return keys.some((key) => Object.getOwnPropertyDescriptor(value, key) !== undefined);
}
function borderPatchSideNeedsProofRecord(patch) {
    return sectionPatchNeedsProofRecord(patch, ['style', 'weight', 'color']);
}
function stylePatchNeedsProofRecord(patch) {
    return (sectionPatchNeedsProofRecord(patch.fill, ['backgroundColor']) ||
        sectionPatchNeedsProofRecord(patch.font, ['family', 'size', 'bold', 'italic', 'underline', 'color']) ||
        sectionPatchNeedsProofRecord(patch.alignment, [
            'horizontal',
            'vertical',
            'wrap',
            'indent',
            'shrinkToFit',
            'readingOrder',
            'textRotation',
            'justifyLastLine',
        ]) ||
        patch.borders === null ||
        borderPatchSideNeedsProofRecord(patch.borders?.top) ||
        borderPatchSideNeedsProofRecord(patch.borders?.right) ||
        borderPatchSideNeedsProofRecord(patch.borders?.bottom) ||
        borderPatchSideNeedsProofRecord(patch.borders?.left));
}
function resolveStyleTiles(workbook, range) {
    const bounds = normalizeRange(range);
    const sheetRanges = workbook.listStyleRanges(range.sheetName);
    const rowBoundaries = new Set([bounds.startRow, bounds.endRow + 1]);
    const colBoundaries = new Set([bounds.startCol, bounds.endCol + 1]);
    sheetRanges.forEach((record) => {
        const clipped = intersectRangeBounds(record.range, bounds);
        if (!clipped) {
            return;
        }
        rowBoundaries.add(clipped.startRow);
        rowBoundaries.add(clipped.endRow + 1);
        colBoundaries.add(clipped.startCol);
        colBoundaries.add(clipped.endCol + 1);
    });
    const rows = [...rowBoundaries].toSorted((left, right) => left - right);
    const cols = [...colBoundaries].toSorted((left, right) => left - right);
    const tiles = [];
    for (let rowIndex = 0; rowIndex < rows.length - 1; rowIndex += 1) {
        const startRow = rows[rowIndex];
        const endRow = rows[rowIndex + 1] - 1;
        for (let colIndex = 0; colIndex < cols.length - 1; colIndex += 1) {
            const startCol = cols[colIndex];
            const endCol = cols[colIndex + 1] - 1;
            tiles.push({
                range: {
                    sheetName: range.sheetName,
                    startAddress: formatAddress(startRow, startCol),
                    endAddress: formatAddress(endRow, endCol),
                },
                styleId: workbook.getStyleId(range.sheetName, startRow, startCol),
            });
        }
    }
    return tiles;
}
function resolveOverlappingStyleTiles(workbook, range) {
    const bounds = normalizeRange(range);
    return workbook
        .listStyleRanges(range.sheetName)
        .filter((record) => intersectRangeBounds(record.range, bounds) !== undefined)
        .map((record) => ({
        range: { ...record.range },
        styleId: record.styleId,
    }));
}
function coalesceStyleTiles(tiles) {
    const sorted = [...tiles].toSorted((left, right) => {
        const leftStart = parseCellAddress(left.range.startAddress, left.range.sheetName);
        const leftEnd = parseCellAddress(left.range.endAddress, left.range.sheetName);
        const rightStart = parseCellAddress(right.range.startAddress, right.range.sheetName);
        const rightEnd = parseCellAddress(right.range.endAddress, right.range.sheetName);
        const sheetCompare = left.range.sheetName.localeCompare(right.range.sheetName);
        if (sheetCompare !== 0) {
            return sheetCompare;
        }
        if (leftStart.row !== rightStart.row) {
            return leftStart.row - rightStart.row;
        }
        if (leftStart.col !== rightStart.col) {
            return leftStart.col - rightStart.col;
        }
        if (leftEnd.row !== rightEnd.row) {
            return leftEnd.row - rightEnd.row;
        }
        return leftEnd.col - rightEnd.col;
    });
    const merged = [];
    sorted.forEach((tile) => {
        const start = parseCellAddress(tile.range.startAddress, tile.range.sheetName);
        const end = parseCellAddress(tile.range.endAddress, tile.range.sheetName);
        const startRow = Math.min(start.row, end.row);
        const endRow = Math.max(start.row, end.row);
        const startCol = Math.min(start.col, end.col);
        const endCol = Math.max(start.col, end.col);
        const previous = merged[merged.length - 1];
        if (previous &&
            previous.sheetName === tile.range.sheetName &&
            previous.styleId === tile.styleId &&
            ((previous.startCol === startCol && previous.endCol === endCol && startRow <= previous.endRow + 1) ||
                (previous.startRow === startRow && previous.endRow === endRow && startCol <= previous.endCol + 1))) {
            previous.startRow = Math.min(previous.startRow, startRow);
            previous.endRow = Math.max(previous.endRow, endRow);
            previous.startCol = Math.min(previous.startCol, startCol);
            previous.endCol = Math.max(previous.endCol, endCol);
            return;
        }
        merged.push({
            sheetName: tile.range.sheetName,
            styleId: tile.styleId,
            startRow,
            endRow,
            startCol,
            endCol,
        });
    });
    return merged.map((tile) => ({
        range: {
            sheetName: tile.sheetName,
            startAddress: formatAddress(tile.startRow, tile.startCol),
            endAddress: formatAddress(tile.endRow, tile.endCol),
        },
        styleId: tile.styleId,
    }));
}
function resolveFormatTiles(workbook, range) {
    const bounds = normalizeRange(range);
    const sheetRanges = workbook.listFormatRanges(range.sheetName);
    const rowBoundaries = new Set([bounds.startRow, bounds.endRow + 1]);
    const colBoundaries = new Set([bounds.startCol, bounds.endCol + 1]);
    sheetRanges.forEach((record) => {
        const clipped = intersectRangeBounds(record.range, bounds);
        if (!clipped) {
            return;
        }
        rowBoundaries.add(clipped.startRow);
        rowBoundaries.add(clipped.endRow + 1);
        colBoundaries.add(clipped.startCol);
        colBoundaries.add(clipped.endCol + 1);
    });
    const rows = [...rowBoundaries].toSorted((left, right) => left - right);
    const cols = [...colBoundaries].toSorted((left, right) => left - right);
    const tiles = [];
    for (let rowIndex = 0; rowIndex < rows.length - 1; rowIndex += 1) {
        const startRow = rows[rowIndex];
        const endRow = rows[rowIndex + 1] - 1;
        for (let colIndex = 0; colIndex < cols.length - 1; colIndex += 1) {
            const startCol = cols[colIndex];
            const endCol = cols[colIndex + 1] - 1;
            tiles.push({
                range: {
                    sheetName: range.sheetName,
                    startAddress: formatAddress(startRow, startCol),
                    endAddress: formatAddress(endRow, endCol),
                },
                formatId: workbook.getRangeFormatId(range.sheetName, startRow, startCol),
            });
        }
    }
    return tiles;
}
function resolveOverlappingFormatTiles(workbook, range) {
    const bounds = normalizeRange(range);
    return workbook
        .listFormatRanges(range.sheetName)
        .filter((record) => intersectRangeBounds(record.range, bounds) !== undefined)
        .map((record) => ({
        range: { ...record.range },
        formatId: record.formatId,
    }));
}
//# sourceMappingURL=engine-range-format-ops.js.map