"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OperationReplayService = void 0;
/**
 * OperationReplay Service
 * Provides deterministic replay of operations from cache
 */
class OperationReplayService {
    constructor(cache, fingerprint, validator) {
        this.cache = cache;
        this.fingerprint = fingerprint;
        this.validator = validator;
    }
    /**
     * Replay a single operation from cache
     */
    async replay(op, mode = "strict") {
        const startTime = Date.now();
        // Check if cache exists
        const cachedResult = await this.cache.get(op);
        if (!cachedResult) {
            return {
                operationId: op.id,
                operationType: op.type,
                replayed: false,
                source: "failed",
                error: "No cache entry found",
                durationMs: Date.now() - startTime,
            };
        }
        // Validate cache before replay
        const validation = await this.validateReplay(op);
        if (!validation.canReplay) {
            if (mode === "strict") {
                return {
                    operationId: op.id,
                    operationType: op.type,
                    replayed: false,
                    source: "failed",
                    error: `Cache validation failed: ${validation.reasons.join(", ")}`,
                    durationMs: Date.now() - startTime,
                };
            }
            else if (mode === "lenient") {
                // In lenient mode, mark as needing re-execution
                return {
                    operationId: op.id,
                    operationType: op.type,
                    replayed: false,
                    source: "re-executed",
                    error: `Cache invalid, needs re-execution: ${validation.reasons.join(", ")}`,
                    durationMs: Date.now() - startTime,
                };
            }
            else {
                // validate-only mode
                return {
                    operationId: op.id,
                    operationType: op.type,
                    replayed: false,
                    source: "skipped",
                    validationReport: await this.validator.validatePreFlight(op),
                    durationMs: Date.now() - startTime,
                };
            }
        }
        // Replay from cache!
        const cacheEntry = this.cache
            .getAll()
            .find((e) => e.fingerprint === this.fingerprint.generateFingerprint(op));
        return {
            operationId: op.id,
            operationType: op.type,
            replayed: true,
            source: "cache",
            result: cachedResult,
            cacheEntry,
            durationMs: Date.now() - startTime,
        };
    }
    /**
     * Replay a batch of operations
     */
    async replayBatch(ops, mode = "strict") {
        const startTime = Date.now();
        const results = [];
        for (const op of ops) {
            const result = await this.replay(op, mode);
            results.push(result);
        }
        const replayed = results.filter((r) => r.source === "cache").length;
        const reExecuted = results.filter((r) => r.source === "re-executed").length;
        const skipped = results.filter((r) => r.source === "skipped").length;
        const failed = results.filter((r) => r.source === "failed").length;
        const cacheHitRate = ops.length > 0 ? Math.round((replayed / ops.length) * 100) / 100 : 0;
        return {
            totalOperations: ops.length,
            replayed,
            reExecuted,
            skipped,
            failed,
            operations: results,
            totalDurationMs: Date.now() - startTime,
            cacheHitRate,
        };
    }
    /**
     * Validate if an operation can be replayed from cache
     */
    async validateReplay(op) {
        const reasons = [];
        // Check if cache exists
        const hasCacheEntry = await this.cache.has(op);
        if (!hasCacheEntry) {
            reasons.push("No cache entry found");
            return {
                canReplay: false,
                cacheValid: false,
                filesUnchanged: false,
                reasons,
            };
        }
        // Get cache entry
        const fingerprint = this.fingerprint.generateFingerprint(op);
        const cacheEntry = this.cache
            .getAll()
            .find((e) => e.fingerprint === fingerprint);
        if (!cacheEntry) {
            reasons.push("Cache entry not found");
            return {
                canReplay: false,
                cacheValid: false,
                filesUnchanged: false,
                reasons,
            };
        }
        // Check if files have changed (cache handles this internally)
        // If cache.get() returns null, files have changed
        const cachedResult = await this.cache.get(op);
        if (!cachedResult) {
            reasons.push("Cache invalid or files changed");
            return {
                canReplay: false,
                cacheValid: false,
                filesUnchanged: false,
                reasons,
            };
        }
        // Check if operation is idempotent
        const idempotencyCheck = await this.fingerprint.checkIdempotency(op);
        if (!idempotencyCheck.isIdempotent && this.fingerprint.isApplied(op)) {
            reasons.push("Operation is non-idempotent and already applied");
            return {
                canReplay: false,
                cacheValid: true,
                filesUnchanged: true,
                reasons,
            };
        }
        // All checks passed!
        return {
            canReplay: true,
            cacheValid: true,
            filesUnchanged: true,
            reasons: ["Cache valid and ready for replay"],
        };
    }
    /**
     * Get replay report for a batch
     */
    async getReplayReport(ops) {
        const details = [];
        let canReplayCount = 0;
        let needsReExecutionCount = 0;
        let invalidCacheCount = 0;
        for (const op of ops) {
            const validation = await this.validateReplay(op);
            if (validation.canReplay) {
                canReplayCount++;
            }
            else if (!validation.cacheValid) {
                invalidCacheCount++;
            }
            else {
                needsReExecutionCount++;
            }
            details.push({
                operationId: op.id,
                operationType: op.type,
                canReplay: validation.canReplay,
                reason: validation.reasons.join(", "),
            });
        }
        return {
            canReplayAll: canReplayCount === ops.length,
            canReplayCount,
            needsReExecutionCount,
            invalidCacheCount,
            details,
        };
    }
    /**
     * Dry-run replay: Validate without actual replay
     */
    async dryRunReplay(ops) {
        const wouldReplay = [];
        const wouldReExecute = [];
        const wouldFail = [];
        for (const op of ops) {
            const validation = await this.validateReplay(op);
            if (validation.canReplay) {
                wouldReplay.push(op.id);
            }
            else if (!validation.cacheValid) {
                wouldFail.push(op.id);
            }
            else {
                wouldReExecute.push(op.id);
            }
        }
        const cacheHitRate = ops.length > 0 ? Math.round((wouldReplay.length / ops.length) * 100) / 100 : 0;
        return {
            wouldReplay,
            wouldReExecute,
            wouldFail,
            cacheHitRate,
        };
    }
    /**
     * Invalidate cache for operations that need re-execution
     */
    async invalidateStale(ops) {
        let count = 0;
        for (const op of ops) {
            const validation = await this.validateReplay(op);
            if (!validation.canReplay && !validation.cacheValid) {
                const fingerprint = this.fingerprint.generateFingerprint(op);
                await this.cache.invalidate(fingerprint, "file-changed");
                count++;
            }
        }
        return count;
    }
    /**
     * Get cache statistics for a batch
     */
    async getCacheStats(ops) {
        let cached = 0;
        let uncached = 0;
        let stale = 0;
        let fresh = 0;
        let totalAge = 0;
        for (const op of ops) {
            const fingerprint = this.fingerprint.generateFingerprint(op);
            const cacheEntry = this.cache
                .getAll()
                .find((e) => e.fingerprint === fingerprint);
            if (cacheEntry) {
                cached++;
                const age = Date.now() - new Date(cacheEntry.metadata.createdAt).getTime();
                totalAge += age;
                const validation = await this.validateReplay(op);
                if (validation.canReplay) {
                    fresh++;
                }
                else {
                    stale++;
                }
            }
            else {
                uncached++;
            }
        }
        return {
            totalOps: ops.length,
            cached,
            uncached,
            stale,
            fresh,
            avgCacheAge: cached > 0 ? Math.round(totalAge / cached) : 0,
        };
    }
}
exports.OperationReplayService = OperationReplayService;
//# sourceMappingURL=OperationReplay.js.map