/**
 * Async Memory Writer
 * Background worker for processing buffered memory writes with retry logic
 */
import { promises as fs } from 'fs';
import { join } from 'path';
import { homedir } from 'os';
import { MemoryType, ImportanceLevel, MCPToolResultStatus } from '../types/enums.js';
export class CircuitBreaker {
    failureThreshold;
    recoveryTimeout;
    failureCount = 0;
    lastFailureTime = 0;
    state = 'closed';
    constructor(failureThreshold = 5, recoveryTimeout = 60000 // 60 seconds
    ) {
        this.failureThreshold = failureThreshold;
        this.recoveryTimeout = recoveryTimeout;
    }
    recordSuccess() {
        if (this.state === 'half-open') {
            console.info('Circuit breaker closing after successful operation');
            this.state = 'closed';
            this.failureCount = 0;
        }
    }
    recordFailure() {
        this.failureCount++;
        this.lastFailureTime = Date.now();
        if (this.failureCount >= this.failureThreshold) {
            if (this.state !== 'open') {
                console.error(`Circuit breaker opening after ${this.failureCount} failures`);
                this.state = 'open';
            }
        }
    }
    canProceed() {
        if (this.state === 'closed') {
            return true;
        }
        if (this.state === 'open') {
            // Check if recovery timeout has passed
            if (Date.now() - this.lastFailureTime > this.recoveryTimeout) {
                console.info('Circuit breaker entering half-open state');
                this.state = 'half-open';
                return true;
            }
            return false;
        }
        // half-open state
        return true;
    }
    getState() {
        return this.state;
    }
    getFailureCount() {
        return this.failureCount;
    }
}
export class AsyncMemoryWriter {
    buffer;
    memoryCore;
    maxRetries;
    retryDelays;
    circuitBreaker;
    running = false;
    pendingTask = null;
    retryTask = null;
    failureLogPath;
    stats = {
        writesAttempted: 0,
        writesSucceeded: 0,
        writesFailed: 0,
        writesRetried: 0,
        lastSuccessTime: 0,
        lastFailureTime: 0,
    };
    constructor(buffer, memoryCore, maxRetries = 3, retryDelays = [1000, 5000, 30000], // milliseconds
    failureLogPath) {
        this.buffer = buffer;
        this.memoryCore = memoryCore;
        this.maxRetries = maxRetries;
        this.retryDelays = retryDelays;
        this.circuitBreaker = new CircuitBreaker();
        this.failureLogPath = failureLogPath || join(homedir(), '.mcp', 'failed_memories.jsonl');
        this.ensureFailureLogDir();
    }
    async ensureFailureLogDir() {
        const dir = this.failureLogPath.substring(0, this.failureLogPath.lastIndexOf('/'));
        try {
            await fs.mkdir(dir, { recursive: true });
        }
        catch (error) {
            // Directory might already exist
        }
    }
    /**
     * Start background processing
     */
    async start() {
        if (this.running) {
            console.warn('Writer already running');
            return;
        }
        this.running = true;
        // Start processing tasks
        this.pendingTask = this.processPendingLoop();
        this.retryTask = this.processRetryLoop();
        console.info('Async memory writer started');
    }
    /**
     * Stop background processing
     */
    async stop() {
        this.running = false;
        // Wait for tasks to complete
        if (this.pendingTask) {
            await this.pendingTask;
        }
        if (this.retryTask) {
            await this.retryTask;
        }
        console.info('Async memory writer stopped');
    }
    async processPendingLoop() {
        while (this.running) {
            try {
                // Get next item from buffer
                const item = await this.buffer.getNextPending();
                if (!item) {
                    await this.sleep(100); // Short sleep if no items
                    continue;
                }
                // Check circuit breaker
                if (!this.circuitBreaker.canProceed()) {
                    // Circuit is open, retry later
                    await this.scheduleRetry(item, 30000);
                    console.warn(`Circuit open, postponing write for ${item.id}`);
                    continue;
                }
                // Attempt to write
                const success = await this.writeWithVerification(item);
                if (success) {
                    await this.buffer.markCompleted(item.id);
                    this.circuitBreaker.recordSuccess();
                }
                else {
                    await this.handleWriteFailure(item);
                    this.circuitBreaker.recordFailure();
                }
            }
            catch (error) {
                console.error('Error in pending loop:', error);
                await this.sleep(1000);
            }
        }
    }
    async processRetryLoop() {
        while (this.running) {
            try {
                // Check for items to retry
                const item = await this.buffer.getNextRetry();
                if (!item) {
                    await this.sleep(1000); // Sleep if no retries
                    continue;
                }
                // Check circuit breaker
                if (!this.circuitBreaker.canProceed()) {
                    // Put back for later retry
                    await this.scheduleRetry(item, 60000);
                    continue;
                }
                // Attempt to write
                const success = await this.writeWithVerification(item);
                if (success) {
                    await this.buffer.markCompleted(item.id);
                    this.circuitBreaker.recordSuccess();
                }
                else {
                    await this.handleWriteFailure(item);
                    this.circuitBreaker.recordFailure();
                }
            }
            catch (error) {
                console.error('Error in retry loop:', error);
                await this.sleep(1000);
            }
        }
    }
    async writeWithVerification(item) {
        this.stats.writesAttempted++;
        try {
            const memoryData = item.memoryData;
            // Write to database using memory core
            const result = await this.memoryCore.addMemory(memoryData.title || '', memoryData.content || '', memoryData.memoryType || MemoryType.MEMORY, {
                userId: memoryData.userId,
                importance: memoryData.importance || ImportanceLevel.MEDIUM,
                tags: memoryData.tags,
                entityIds: memoryData.entityIds,
                generateEmbedding: memoryData.generateEmbedding !== false,
            });
            // Check result
            if (result.status === MCPToolResultStatus.SUCCESS) {
                this.stats.writesSucceeded++;
                this.stats.lastSuccessTime = Date.now();
                console.debug(`Memory ${item.id} written successfully`);
                return true;
            }
            else {
                // Write failed
                item.lastError = result.error || 'Unknown error';
                console.warn(`Memory ${item.id} write failed: ${item.lastError}`);
                return false;
            }
        }
        catch (error) {
            item.lastError = `Unexpected error: ${String(error)}`;
            console.error(`Unexpected error writing ${item.id}:`, error);
            return false;
        }
    }
    async handleWriteFailure(item) {
        this.stats.writesFailed++;
        this.stats.lastFailureTime = Date.now();
        if (item.attempts >= this.maxRetries) {
            // Permanent failure
            await this.handlePermanentFailure(item);
        }
        else {
            // Schedule retry
            await this.scheduleRetry(item);
        }
    }
    async scheduleRetry(item, delay) {
        if (delay === undefined) {
            // Calculate delay based on attempts
            const delayIndex = Math.min(item.attempts, this.retryDelays.length - 1);
            delay = this.retryDelays[delayIndex];
        }
        await this.buffer.scheduleRetry(item, delay / 1000); // Convert to seconds
        this.stats.writesRetried++;
        console.info(`Scheduled retry for ${item.id} in ${delay}ms (attempt ${item.attempts + 1}/${this.maxRetries})`);
    }
    async handlePermanentFailure(item) {
        await this.buffer.markFailed(item.id, item.lastError || 'Max retries exceeded');
        // Log to failure file for manual recovery
        try {
            const failureRecord = {
                timestamp: new Date().toISOString(),
                memoryId: item.id,
                memoryData: item.memoryData,
                attempts: item.attempts,
                lastError: item.lastError,
                queuedAt: item.timestamp,
            };
            await fs.appendFile(this.failureLogPath, JSON.stringify(failureRecord) + '\n');
            console.error(`Logged permanent failure for ${item.id} to ${this.failureLogPath}`);
        }
        catch (error) {
            console.error(`Failed to log permanent failure for ${item.id}:`, error);
        }
    }
    /**
     * Get writer statistics
     */
    async getStats() {
        const bufferMetrics = await this.buffer.getMetrics();
        return {
            ...this.stats,
            circuitBreakerState: this.circuitBreaker.getState(),
            circuitBreakerFailures: this.circuitBreaker.getFailureCount(),
        };
    }
    sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
}
//# sourceMappingURL=writer.js.map