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());
    }));
};

export class CrossSheetDataSynchronizer {
    constructor(formulaEngine, crossSheetManager) {
        this.changeQueue = [], this.isSyncing = !1, this.syncTimer = null, this.options = {
            immediate: !1,
            batchSize: 100,
            timeout: 50
        }, this.listeners = new Map, this.formulaEngine = formulaEngine, this.crossSheetManager = crossSheetManager;
    }
    registerDataChange(sheet, cells, oldValues, newValues) {
        const event = {
            sheet: sheet,
            cells: cells,
            oldValues: oldValues,
            newValues: newValues,
            timestamp: Date.now()
        };
        this.changeQueue.push(event), this.options.immediate ? this.processChanges() : this.scheduleSync();
    }
    scheduleSync() {
        this.syncTimer && clearTimeout(this.syncTimer), this.syncTimer = setTimeout((() => {
            this.processChanges();
        }), this.options.timeout);
    }
    processChanges() {
        return __awaiter(this, void 0, void 0, (function*() {
            if (!this.isSyncing && 0 !== this.changeQueue.length) {
                this.isSyncing = !0;
                try {
                    const batch = this.changeQueue.splice(0, this.options.batchSize), changesBySheet = this.groupChangesBySheet(batch);
                    for (const [targetSheet, changes] of changesBySheet.entries()) yield this.syncSheetChanges(targetSheet, changes);
                    for (const event of batch) this.emitDataChange(event);
                } catch (error) {} finally {
                    this.isSyncing = !1, this.changeQueue.length > 0 && this.scheduleSync();
                }
            }
        }));
    }
    groupChangesBySheet(events) {
        const groups = new Map;
        for (const event of events) {
            const existing = groups.get(event.sheet) || [];
            existing.push(event), groups.set(event.sheet, existing);
        }
        return groups;
    }
    syncSheetChanges(targetSheet, changes) {
        return __awaiter(this, void 0, void 0, (function*() {
            const allChangedCells = [], allOldValues = [], allNewValues = [];
            for (const change of changes) allChangedCells.push(...change.cells), allOldValues.push(...change.oldValues), 
            allNewValues.push(...change.newValues);
            yield this.crossSheetManager.updateCrossSheetReferences(targetSheet, allChangedCells);
        }));
    }
    getCrossSheetDependencies() {
        const dependencies = new Map, allSheets = this.formulaEngine.getAllSheets();
        for (const sheetInfo of allSheets) {
            const targetSheets = this.crossSheetManager.getCrossSheetDependencies(sheetInfo.key).map((dep => dep.precedentSheet));
            dependencies.set(sheetInfo.key, targetSheets);
        }
        return dependencies;
    }
    validateCrossSheetReferences() {
        const errors = [], allSheets = this.formulaEngine.getAllSheets();
        for (const sheetInfo of allSheets) {
            const validation = this.crossSheetManager.validateCrossSheetReferences(sheetInfo.key);
            validation.valid || errors.push(...validation.errors);
            const circularDeps = this.detectCircularDependencies(sheetInfo.key, new Set);
            circularDeps.length > 0 && errors.push(`Circular dependency detected in sheet ${sheetInfo.key}: ${circularDeps.join(" -> ")}`);
        }
        return {
            valid: 0 === errors.length,
            errors: errors
        };
    }
    detectCircularDependencies(sheet, visited) {
        if (visited.has(sheet)) return [ sheet ];
        visited.add(sheet);
        const dependencies = this.crossSheetManager.getCrossSheetDependencies(sheet);
        for (const dep of dependencies) {
            const cycle = this.detectCircularDependencies(dep.precedentSheet, new Set(visited));
            if (cycle.length > 0) return [ sheet, ...cycle ];
        }
        return [];
    }
    addDataChangeListener(sheet, listener) {
        this.listeners.has(sheet) || this.listeners.set(sheet, new Set), this.listeners.get(sheet).add(listener);
    }
    removeDataChangeListener(sheet, listener) {
        const listeners = this.listeners.get(sheet);
        listeners && (listeners.delete(listener), 0 === listeners.size && this.listeners.delete(sheet));
    }
    emitDataChange(event) {
        const listeners = this.listeners.get(event.sheet);
        if (listeners) for (const listener of listeners) try {
            listener(event);
        } catch (error) {}
        const globalListeners = this.listeners.get("*");
        if (globalListeners) for (const listener of globalListeners) try {
            listener(event);
        } catch (error) {}
    }
    forceSync() {
        return __awaiter(this, void 0, void 0, (function*() {
            this.syncTimer && (clearTimeout(this.syncTimer), this.syncTimer = null), yield this.processChanges();
        }));
    }
    clearSyncQueue() {
        this.changeQueue = [], this.syncTimer && (clearTimeout(this.syncTimer), this.syncTimer = null);
    }
    getSyncStatus() {
        var _a;
        return {
            pendingChanges: this.changeQueue.length,
            isSyncing: this.isSyncing,
            lastSyncTime: this.changeQueue.length > 0 && (null === (_a = this.changeQueue[this.changeQueue.length - 1]) || void 0 === _a ? void 0 : _a.timestamp) || null
        };
    }
    updateSyncOptions(options) {
        this.options = Object.assign(Object.assign({}, this.options), options);
    }
    destroy() {
        this.clearSyncQueue(), this.listeners.clear(), this.crossSheetManager.clearCache();
    }
}
//# sourceMappingURL=cross-sheet-data-synchronizer.js.map