"use strict";

var __awaiter = this && this.__awaiter || function(thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))((function(resolve, reject) {
        function fulfilled(value) {
            try {
                step(generator.next(value));
            } catch (e) {
                reject(e);
            }
        }
        function rejected(value) {
            try {
                step(generator.throw(value));
            } catch (e) {
                reject(e);
            }
        }
        function step(result) {
            var value;
            result.done ? resolve(result.value) : (value = result.value, value instanceof P ? value : new P((function(resolve) {
                resolve(value);
            }))).then(fulfilled, rejected);
        }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    }));
};

Object.defineProperty(exports, "__esModule", {
    value: !0
}), exports.CrossSheetFormulaManager = void 0;

class CrossSheetFormulaManager {
    constructor(formulaEngine, sheetManager) {
        this.crossSheetDependencies = new Map, this.reverseCrossSheetDependencies = new Map, 
        this.calculationCache = new Map, this.CACHE_TTL = 1e3, this.formulaEngine = formulaEngine, 
        this.sheetManager = sheetManager;
    }
    registerCrossSheetReference(formula, sourceCell) {
        const references = [], targetSheets = this.extractTargetSheets(formula);
        for (const targetSheet of targetSheets) {
            const targetCells = this.extractTargetCells(formula, targetSheet);
            targetCells.length > 0 && (references.push({
                sourceSheet: sourceCell.sheet,
                targetSheet: targetSheet,
                sourceCell: sourceCell,
                targetCells: targetCells,
                formula: formula
            }), this.updateCrossSheetDependency(sourceCell.sheet, targetSheet, sourceCell, targetCells));
        }
        return references;
    }
    isValidSheet(sheetName) {
        if (this.sheetManager) {
            return this.sheetManager.getAllSheets().some((sheet => sheet.sheetTitle.toLowerCase() === sheetName.toLowerCase()));
        }
        return this.formulaEngine.getAllSheets().some((sheet => sheet.title.toLowerCase() === sheetName.toLowerCase()));
    }
    extractTargetSheets(formula) {
        const sheets = new Set, quotedSheetPattern = /'([^']+)'[!！]/g, unquotedSheetPattern = /([A-Za-z0-9_\s一-龥]+)[!！]/g;
        let match;
        for (;null !== (match = quotedSheetPattern.exec(formula)); ) {
            const sheetName = match[1];
            sheetName && this.isValidSheet(sheetName) && sheets.add(sheetName);
        }
        for (;null !== (match = unquotedSheetPattern.exec(formula)); ) {
            const sheetName = match[1];
            sheetName && this.isValidSheet(sheetName) && sheets.add(sheetName);
        }
        return Array.from(sheets);
    }
    extractTargetCells(formula, targetSheet) {
        const cells = [], escapedSheetName = targetSheet.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), cellPattern = new RegExp(`'?${escapedSheetName}'?[!！]([A-Z]+[0-9]+)(?:\\s*:\\s*(?:'?${escapedSheetName}'?[!！])?([A-Z]+[0-9]+))?`, "g");
        let match;
        for (;null !== (match = cellPattern.exec(formula)); ) if (match[2]) {
            const startCell = this.parseA1Notation(match[1]), endCell = this.parseA1Notation(match[2]);
            for (let row = Math.min(startCell.row, endCell.row); row <= Math.max(startCell.row, endCell.row); row++) for (let col = Math.min(startCell.col, endCell.col); col <= Math.max(startCell.col, endCell.col); col++) cells.push({
                sheet: targetSheet,
                row: row,
                col: col
            });
        } else {
            const cell = this.parseA1Notation(match[1]);
            cells.push({
                sheet: targetSheet,
                row: cell.row,
                col: cell.col
            });
        }
        return cells;
    }
    updateCrossSheetDependency(sourceSheet, targetSheet, _sourceCell, _targetCells) {
        this.crossSheetDependencies.has(sourceSheet) || this.crossSheetDependencies.set(sourceSheet, new Set);
        const sourceDeps = this.crossSheetDependencies.get(sourceSheet);
        sourceDeps && sourceDeps.add(targetSheet), this.reverseCrossSheetDependencies.has(targetSheet) || this.reverseCrossSheetDependencies.set(targetSheet, new Set);
        const targetDeps = this.reverseCrossSheetDependencies.get(targetSheet);
        targetDeps && targetDeps.add(sourceSheet);
    }
    getCrossSheetDependencies(sheet) {
        const dependencies = [], targetSheets = this.crossSheetDependencies.get(sheet);
        if (targetSheets) for (const targetSheet of targetSheets) {
            const dependentCells = this.getDependentCells(sheet, targetSheet), precedentCells = this.getPrecedentCells(targetSheet, sheet);
            dependencies.push({
                dependentSheet: sheet,
                precedentSheet: targetSheet,
                dependentCells: dependentCells,
                precedentCells: precedentCells
            });
        }
        return dependencies;
    }
    getDependentCells(dependentSheet, precedentSheet) {
        const cells = [], allSheets = this.formulaEngine.getAllSheets();
        for (const sheetInfo of allSheets) if (sheetInfo.key === dependentSheet) {
            const formulas = this.formulaEngine.exportFormulas(sheetInfo.key);
            for (const [cellRef, formula] of Object.entries(formulas)) if (formula.includes(`${precedentSheet}!`)) {
                const cell = this.parseA1Notation(cellRef);
                cells.push({
                    sheet: dependentSheet,
                    row: cell.row,
                    col: cell.col
                });
            }
        }
        return cells;
    }
    getPrecedentCells(precedentSheet, dependentSheet) {
        const cells = [], formulas = this.formulaEngine.exportFormulas(dependentSheet);
        for (const formula of Object.values(formulas)) if (formula.includes(`${precedentSheet}!`)) {
            const targetCells = this.extractTargetCells(formula, precedentSheet);
            cells.push(...targetCells);
        }
        return cells;
    }
    updateCrossSheetReferences(targetSheet, changedCells) {
        return __awaiter(this, void 0, void 0, (function*() {
            const dependentSheets = this.reverseCrossSheetDependencies.get(targetSheet);
            if (!dependentSheets || 0 === dependentSheets.size) return;
            const cellsToRecalculate = [];
            for (const dependentSheet of dependentSheets) {
                const formulas = this.formulaEngine.exportFormulas(dependentSheet);
                for (const [cellRef, formula] of Object.entries(formulas)) if (this.formulaReferencesCells(formula, targetSheet, changedCells)) {
                    const cell = this.parseA1Notation(cellRef);
                    cellsToRecalculate.push({
                        sheet: dependentSheet,
                        row: cell.row,
                        col: cell.col
                    });
                }
            }
            for (const cell of cellsToRecalculate) yield this.recalculateFormula(cell);
        }));
    }
    formulaReferencesCells(formula, targetSheet, targetCells) {
        return this.extractTargetCells(formula, targetSheet).some((refCell => targetCells.some((targetCell => refCell.sheet === targetCell.sheet && refCell.row === targetCell.row && refCell.col === targetCell.col))));
    }
    recalculateFormula(cell) {
        return __awaiter(this, void 0, void 0, (function*() {
            try {
                const formula = this.formulaEngine.getCellFormula(cell);
                if (formula) {
                    const result = this.formulaEngine.calculateFormula(formula), cacheKey = this.getCacheKey(cell);
                    this.calculationCache.set(cacheKey, {
                        value: result.value,
                        timestamp: Date.now()
                    }), this.recalculateDependents(cell);
                }
            } catch (error) {}
        }));
    }
    recalculateDependents(cell) {
        const dependents = this.formulaEngine.getCellDependents(cell);
        for (const dependent of dependents) this.recalculateFormula(dependent);
    }
    validateCrossSheetReferences(sheet) {
        const errors = [], formulas = this.formulaEngine.exportFormulas(sheet);
        for (const [cellRef, formula] of Object.entries(formulas)) {
            const targetSheets = this.extractTargetSheets(formula);
            for (const targetSheet of targetSheets) {
                if (!this.formulaEngine.getAllSheets().some((s => s.key === targetSheet))) {
                    errors.push(`Invalid sheet reference in ${cellRef}: ${targetSheet}`);
                    continue;
                }
                const targetCells = this.extractTargetCells(formula, targetSheet);
                for (const targetCell of targetCells) {
                    this.formulaEngine.getCellValue(targetCell).error && errors.push(`Invalid cell reference in ${cellRef}: ${targetSheet}!${this.getA1Notation(targetCell.row, targetCell.col)}`);
                }
            }
        }
        return {
            valid: 0 === errors.length,
            errors: errors
        };
    }
    getCachedValue(cell) {
        const cacheKey = this.getCacheKey(cell), cached = this.calculationCache.get(cacheKey);
        return cached && Date.now() - cached.timestamp < this.CACHE_TTL ? cached.value : null;
    }
    clearCache() {
        this.calculationCache.clear();
    }
    clearSheetDependencies(sheet) {
        this.crossSheetDependencies.delete(sheet);
        for (const [targetSheet, sourceSheets] of this.reverseCrossSheetDependencies.entries()) sourceSheets.delete(sheet), 
        0 === sourceSheets.size && this.reverseCrossSheetDependencies.delete(targetSheet);
        for (const [cacheKey] of this.calculationCache.entries()) cacheKey.startsWith(`${sheet}!`) && this.calculationCache.delete(cacheKey);
    }
    parseA1Notation(a1Notation) {
        const match = a1Notation.match(/^([A-Z]+)([0-9]+)$/);
        if (!match) throw new Error(`Invalid cell reference: ${a1Notation}`);
        const colLetters = match[1], rowNumber = parseInt(match[2], 10);
        let col = 0;
        for (let i = 0; i < colLetters.length; i++) col = 26 * col + (colLetters.charCodeAt(i) - 65);
        return {
            row: rowNumber - 1,
            col: col
        };
    }
    getA1Notation(row, col) {
        let colStr = "", tempCol = col;
        do {
            colStr = String.fromCharCode(65 + tempCol % 26) + colStr, tempCol = Math.floor(tempCol / 26) - 1;
        } while (tempCol >= 0);
        return `${colStr}${row + 1}`;
    }
    getCacheKey(cell) {
        return `${cell.sheet}!${this.getA1Notation(cell.row, cell.col)}`;
    }
    destroy() {
        this.clearCache(), this.crossSheetDependencies.clear(), this.reverseCrossSheetDependencies.clear();
    }
}

exports.CrossSheetFormulaManager = CrossSheetFormulaManager;
//# sourceMappingURL=cross-sheet-formula-manager.js.map