/**
 * LangChain service for advanced AI operations
 * Provides document chunking, vector storage, and RAG capabilities
 * Enhanced with intelligent robustness: circuit breakers, adaptive rate limiting,
 * intelligent caching, predictive failure detection, and graceful degradation
 */
import { StringOutputParser } from '@langchain/core/output_parsers';
import { PromptTemplate } from '@langchain/core/prompts';
import { RunnableSequence } from '@langchain/core/runnables';
import { ChatOpenAI, OpenAIEmbeddings } from '@langchain/openai';
import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters';
import { LangChainHMSVectorStore } from './hms-vector-store.js';
import { createError, ErrorCode } from '../../core/errors.js';
import { getLogger } from '../../core/logger.js';
import { AdaptiveTimeout, ProgressIndicators } from '../../utils/adaptive-timeout.js';
import { AsyncUtils } from '../../utils/shared-patterns.js';
import { RateLimiter, formatDuration } from '../../utils/common.js';
export class LangChainService {
    constructor(apiKey) {
        this.vectorStore = null;
        this.contexts = new Map();
        this.operationMetrics = new Map();
        this.intelligentCache = new Map();
        this.healthScore = 1.0;
        this.degradationLevel = 'none';
        this.fallbackResponses = new Map();
        this.healthInterval = null;
        this.cacheCleanupInterval = null;
        this.predictionModel = {
            failures: [],
            successes: [],
            patterns: [],
        };
        this.logger = getLogger('langchain-service');
        if (!apiKey && !process.env.OPENAI_API_KEY) {
            throw createError(ErrorCode.CONFIGURATION_ERROR, null, 'OpenAI API key required for LangChain service');
        }
        // Initialize OpenAI Chat LLM
        this.llm = new ChatOpenAI({
            openAIApiKey: apiKey || process.env.OPENAI_API_KEY,
            temperature: 0.7,
            modelName: 'gpt-4-turbo-preview',
        });
        // Initialize embeddings
        this.embeddings = new OpenAIEmbeddings({
            openAIApiKey: apiKey || process.env.OPENAI_API_KEY,
        });
        // Initialize text splitter with manuscript-optimized settings
        this.textSplitter = new RecursiveCharacterTextSplitter({
            chunkSize: 2000,
            chunkOverlap: 200,
            separators: ['\n\n\n', '\n\n', '\n', '. ', ' ', ''],
        });
        // Initialize intelligent robustness systems
        this.circuitBreaker = {
            state: 'CLOSED',
            failureCount: 0,
            lastFailureTime: 0,
            firstFailureTime: 0,
            successCount: 0,
            nextAttemptTime: 0,
        };
        this.rateLimiter = {
            tokensPerMinute: 3500,
            currentTokens: 3500,
            lastRefill: Date.now(),
            adaptiveMultiplier: 1.0,
            successRate: 1.0,
        };
        this.performanceMetrics = {
            latency: [],
            throughput: 0,
            errorRate: 0,
            lastUpdated: Date.now(),
        };
        // Initialize utility rate limiter (3500 tokens per minute = ~58 per second)
        this.utilRateLimiter = new RateLimiter(58, 1000);
        // Start background health monitoring
        this.startHealthMonitoring();
        this.startCacheCleanup();
        this.initializeFallbacks();
    }
    /**
     * Enhanced operation metrics tracking using common utilities
     */
    updateOperationMetrics(operationName, executionTime) {
        const existing = this.operationMetrics.get(operationName) || { totalTime: 0, callCount: 0 };
        existing.totalTime += executionTime;
        existing.callCount += 1;
        this.operationMetrics.set(operationName, existing);
        this.logger.debug(`Operation ${operationName} completed in ${formatDuration(executionTime)}`, {
            averageTime: formatDuration(existing.totalTime / existing.callCount),
            callCount: existing.callCount,
        });
    }
    /**
     * Enhanced circuit breaker implementation with utility integration
     */
    async withCircuitBreaker(operation, operationName) {
        const now = Date.now();
        // Check circuit breaker state
        if (this.circuitBreaker.state === 'OPEN') {
            if (now < this.circuitBreaker.nextAttemptTime) {
                throw createError(ErrorCode.AI_SERVICE_ERROR, null, `Circuit breaker OPEN for ${operationName}. Next attempt at ${new Date(this.circuitBreaker.nextAttemptTime).toISOString()}`);
            }
            // Transition to HALF_OPEN
            this.circuitBreaker.state = 'HALF_OPEN';
            this.circuitBreaker.successCount = 0;
            this.logger.info(`Circuit breaker HALF_OPEN for ${operationName}`);
        }
        try {
            const startTime = Date.now();
            const result = await operation();
            const latency = Date.now() - startTime;
            // Record success
            this.recordSuccess(operationName, latency);
            // Update circuit breaker on success
            if (this.circuitBreaker.state === 'HALF_OPEN') {
                this.circuitBreaker.successCount++;
                if (this.circuitBreaker.successCount >= 3) {
                    this.circuitBreaker.state = 'CLOSED';
                    this.circuitBreaker.failureCount = 0;
                    this.circuitBreaker.firstFailureTime = 0;
                    this.logger.info(`Circuit breaker CLOSED for ${operationName}`);
                }
            }
            return result;
        }
        catch (error) {
            this.recordFailure(operationName, error);
            // Update circuit breaker on failure
            this.circuitBreaker.lastFailureTime = now;
            const timeWindow = 60000; // 1 minute
            // Reset the window if first failure was too long ago
            if (now - this.circuitBreaker.firstFailureTime > timeWindow) {
                this.circuitBreaker.failureCount = 0;
                this.circuitBreaker.firstFailureTime = now;
            }
            if (this.circuitBreaker.failureCount === 0) {
                this.circuitBreaker.firstFailureTime = now;
            }
            this.circuitBreaker.failureCount++;
            // Determine if we should open the circuit
            const failureThreshold = Math.max(3, Math.ceil(5 * (1 - this.healthScore)));
            if (this.circuitBreaker.failureCount >= failureThreshold &&
                now - this.circuitBreaker.firstFailureTime < timeWindow) {
                this.circuitBreaker.state = 'OPEN';
                this.circuitBreaker.nextAttemptTime =
                    now + 30000 * this.circuitBreaker.failureCount; // Exponential backoff
                this.logger.warn(`Circuit breaker OPEN for ${operationName}. Failures: ${this.circuitBreaker.failureCount}`);
            }
            throw error;
        }
    }
    /**
     * Adaptive rate limiter that adjusts based on success rates and system health
     */
    async checkRateLimit(estimatedTokens = 1000) {
        // Apply utility rate limiter first (for basic request throttling)
        while (!this.utilRateLimiter.tryRemove()) {
            await new Promise((resolve) => setTimeout(resolve, 100));
        }
        const now = Date.now();
        const timeSinceRefill = now - this.rateLimiter.lastRefill;
        // Refill tokens based on time elapsed
        if (timeSinceRefill > 0) {
            const tokensToAdd = Math.floor((timeSinceRefill / 60000) * this.rateLimiter.tokensPerMinute);
            this.rateLimiter.currentTokens = Math.min(this.rateLimiter.tokensPerMinute, this.rateLimiter.currentTokens + tokensToAdd);
            this.rateLimiter.lastRefill = now;
        }
        // Adjust rate based on health and success rate
        const adjustedTokens = Math.ceil(estimatedTokens / (this.healthScore * this.rateLimiter.successRate));
        if (this.rateLimiter.currentTokens < adjustedTokens) {
            const waitTime = Math.ceil((adjustedTokens - this.rateLimiter.currentTokens) *
                (60000 / this.rateLimiter.tokensPerMinute));
            this.logger.debug(`Rate limit hit, waiting ${waitTime}ms. Health: ${this.healthScore}, Success rate: ${this.rateLimiter.successRate}`);
            // Instead of waiting, trigger graceful degradation if wait is too long
            if (waitTime > 5000) {
                this.adjustDegradationLevel('partial');
                throw createError(ErrorCode.AI_SERVICE_ERROR, null, `Rate limit exceeded, degrading service quality. Estimated wait: ${waitTime}ms`);
            }
            const rateLimitTimeout = new AdaptiveTimeout({
                operation: 'rate-limit-wait',
                baseTimeout: waitTime,
                maxTimeout: waitTime * 2,
                stallTimeout: waitTime + 10000,
                progressIndicators: [ProgressIndicators.networkProgress('api.openai.com', 443)],
            });
            await rateLimitTimeout.wait(AsyncUtils.sleep(waitTime));
        }
        this.rateLimiter.currentTokens -= adjustedTokens;
    }
    /**
     * Intelligent cache with adaptive TTL and LRU eviction
     */
    getCached(key) {
        const entry = this.intelligentCache.get(key);
        if (!entry)
            return null;
        const now = Date.now();
        if (now - entry.timestamp > entry.ttl) {
            this.intelligentCache.delete(key);
            return null;
        }
        // Update access patterns for cache intelligence
        entry.hitCount++;
        entry.lastAccessed = now;
        return entry.data;
    }
    setCached(key, data, baseTTL = 300000) {
        // Adaptive TTL based on access patterns and system health
        const existing = this.intelligentCache.get(key);
        const hitRate = existing ? existing.hitCount / 10 : 0.1;
        const healthMultiplier = 1 + (this.healthScore - 0.5) * 2; // 0.0 to 2.0
        const adaptiveTTL = baseTTL * hitRate * healthMultiplier;
        const entry = {
            data,
            timestamp: Date.now(),
            ttl: adaptiveTTL,
            hitCount: existing?.hitCount || 0,
            lastAccessed: Date.now(),
            size: JSON.stringify(data).length,
        };
        // LRU eviction if cache is too large
        if (this.intelligentCache.size > 100) {
            this.evictLRU();
        }
        this.intelligentCache.set(key, entry);
    }
    evictLRU() {
        let oldestKey = '';
        let oldestTime = Date.now();
        for (const [key, entry] of this.intelligentCache.entries()) {
            if (entry.lastAccessed < oldestTime) {
                oldestTime = entry.lastAccessed;
                oldestKey = key;
            }
        }
        if (oldestKey) {
            this.intelligentCache.delete(oldestKey);
        }
    }
    /**
     * Predictive failure detection using pattern analysis
     */
    predictFailureRisk() {
        const recentFailures = this.predictionModel.failures.slice(-10);
        const recentSuccesses = this.predictionModel.successes.slice(-10);
        if (recentFailures.length === 0)
            return 0;
        // Analyze failure patterns
        const failureRate = recentFailures.length / (recentFailures.length + recentSuccesses.length);
        const timePattern = this.analyzeTimePatterns(recentFailures);
        const escalationPattern = this.detectEscalation(recentFailures);
        return Math.min(1.0, failureRate * 0.5 + timePattern * 0.3 + escalationPattern * 0.2);
    }
    analyzeTimePatterns(failures) {
        if (failures.length < 3)
            return 0;
        const intervals = [];
        for (let i = 1; i < failures.length; i++) {
            intervals.push(failures[i] - failures[i - 1]);
        }
        // Look for decreasing intervals (accelerating failures)
        let decreasingCount = 0;
        for (let i = 1; i < intervals.length; i++) {
            if (intervals[i] < intervals[i - 1])
                decreasingCount++;
        }
        return decreasingCount / (intervals.length - 1);
    }
    detectEscalation(failures) {
        if (failures.length < 5)
            return 0;
        const recent = failures.slice(-3);
        const baseline = failures.slice(-10, -3);
        const recentAvg = recent.reduce((a, b) => a + b, 0) / recent.length;
        const baselineAvg = baseline.reduce((a, b) => a + b, 0) / baseline.length;
        return recentAvg > baselineAvg ? Math.min(1.0, (recentAvg - baselineAvg) / baselineAvg) : 0;
    }
    /**
     * Graceful degradation system
     */
    adjustDegradationLevel(level) {
        if (this.degradationLevel !== level) {
            this.logger.info(`Service degradation level changed from ${this.degradationLevel} to ${level}`);
            this.degradationLevel = level;
            // Adjust operational parameters based on degradation level
            switch (level) {
                case 'partial':
                    this.rateLimiter.adaptiveMultiplier = 0.5; // Reduce rate
                    break;
                case 'full':
                    this.rateLimiter.adaptiveMultiplier = 0.1; // Minimal rate
                    break;
                default:
                    this.rateLimiter.adaptiveMultiplier = 1.0;
            }
        }
    }
    async getFallbackResponse(operation, context) {
        const fallbackKey = `${operation}_${JSON.stringify(context).slice(0, 100)}`;
        const cached = this.fallbackResponses.get(fallbackKey);
        if (cached) {
            this.logger.info(`Using fallback response for ${operation}`);
            return cached;
        }
        // Generate synthetic fallback based on operation type
        switch (operation) {
            case 'summarizeChapter':
                return 'Chapter summary temporarily unavailable due to service constraints.';
            case 'generateWithContext':
                return 'AI-generated content is currently limited. Please try again later or use manual editing.';
            case 'analyzeWritingStyle':
                return { analysis: 'Style analysis unavailable', confidence: 0 };
            default:
                return null;
        }
    }
    /**
     * Health monitoring and metrics recording
     */
    recordSuccess(operation, latency) {
        // Record success in prediction model
        this.predictionModel.successes.push(Date.now());
        if (this.predictionModel.successes.length > 50) {
            this.predictionModel.successes = this.predictionModel.successes.slice(-50);
        }
        // Update operation-specific metrics
        this.updateOperationMetrics(operation, latency);
        this.performanceMetrics.latency.push(latency);
        if (this.performanceMetrics.latency.length > 100) {
            this.performanceMetrics.latency = this.performanceMetrics.latency.slice(-100);
        }
        this.updateHealthScore();
        this.rateLimiter.successRate = Math.min(1.0, this.rateLimiter.successRate + 0.01);
    }
    recordFailure(operation, error) {
        this.predictionModel.failures.push(Date.now());
        if (this.predictionModel.failures.length > 50) {
            this.predictionModel.failures = this.predictionModel.failures.slice(-50);
        }
        this.updateHealthScore();
        this.rateLimiter.successRate = Math.max(0.1, this.rateLimiter.successRate - 0.05);
        // Store failure patterns for prediction
        const pattern = `${operation}:${error.message.substring(0, 50)}`;
        if (!this.predictionModel.patterns.includes(pattern)) {
            this.predictionModel.patterns.push(pattern);
            if (this.predictionModel.patterns.length > 100) {
                this.predictionModel.patterns = this.predictionModel.patterns.slice(-100);
            }
        }
    }
    updateHealthScore() {
        const recentFailures = this.predictionModel.failures.filter((f) => Date.now() - f < 300000); // 5 minutes
        const recentSuccesses = this.predictionModel.successes.filter((s) => Date.now() - s < 300000);
        const total = recentFailures.length + recentSuccesses.length;
        if (total === 0) {
            this.healthScore = 1.0;
        }
        else {
            const successRate = recentSuccesses.length / total;
            const latencyPenalty = this.getLatencyPenalty();
            const predictivePenalty = this.predictFailureRisk();
            this.healthScore = Math.max(0.1, successRate - latencyPenalty - predictivePenalty * 0.3);
        }
        // Adjust degradation based on health score
        if (this.healthScore < 0.3) {
            this.adjustDegradationLevel('full');
        }
        else if (this.healthScore < 0.7) {
            this.adjustDegradationLevel('partial');
        }
        else {
            this.adjustDegradationLevel('none');
        }
    }
    getLatencyPenalty() {
        if (this.performanceMetrics.latency.length === 0)
            return 0;
        const avgLatency = this.performanceMetrics.latency.reduce((a, b) => a + b, 0) /
            this.performanceMetrics.latency.length;
        const expectedLatency = 3000; // 3 seconds baseline
        if (avgLatency <= expectedLatency)
            return 0;
        return Math.min(0.5, (avgLatency - expectedLatency) / (expectedLatency * 2));
    }
    /**
     * Background monitoring processes
     */
    startHealthMonitoring() {
        this.healthInterval = setInterval(() => {
            this.updateHealthScore();
            const riskScore = this.predictFailureRisk();
            if (riskScore > 0.7) {
                this.logger.warn(`High failure risk detected: ${riskScore.toFixed(2)}`);
                this.adjustDegradationLevel('partial');
            }
            this.logger.debug(`Health Score: ${this.healthScore.toFixed(2)}, Failure Risk: ${riskScore.toFixed(2)}, Degradation: ${this.degradationLevel}`);
        }, 30000); // Every 30 seconds
    }
    startCacheCleanup() {
        this.cacheCleanupInterval = setInterval(() => {
            const now = Date.now();
            for (const [key, entry] of this.intelligentCache.entries()) {
                if (now - entry.timestamp > entry.ttl) {
                    this.intelligentCache.delete(key);
                }
            }
        }, 60000); // Every minute
    }
    initializeFallbacks() {
        // Pre-populate common fallback responses
        this.fallbackResponses.set('summarizeChapter_default', 'Chapter summary temporarily unavailable. Please try again later.');
        this.fallbackResponses.set('generateWithContext_default', 'AI assistance is currently limited. Consider manual editing.');
        this.fallbackResponses.set('analyzeWritingStyle_default', {
            tone: 'neutral',
            complexity: 'medium',
            confidence: 0.1,
        });
    }
    destroy() {
        if (this.healthInterval)
            clearInterval(this.healthInterval);
        if (this.cacheCleanupInterval)
            clearInterval(this.cacheCleanupInterval);
        this.healthInterval = null;
        this.cacheCleanupInterval = null;
        this.intelligentCache.clear();
    }
    /**
     * Process and chunk a document for vector storage
     */
    async processDocument(document, options = {}) {
        return this.withCircuitBreaker(async () => {
            const cacheKey = `processDoc_${document.id}_${JSON.stringify(options)}`;
            const cached = this.getCached(cacheKey);
            if (cached)
                return cached;
            // Use configured text splitter or create a custom one if options are different
            const splitter = options.chunkSize || options.chunkOverlap || options.separators
                ? new RecursiveCharacterTextSplitter({
                    chunkSize: options.chunkSize || 2000,
                    chunkOverlap: options.chunkOverlap || 200,
                    separators: options.separators || [
                        '\n\n\n',
                        '\n\n',
                        '\n',
                        '. ',
                        ' ',
                        '',
                    ],
                })
                : this.textSplitter;
            const chunks = await splitter.createDocuments([document.content || ''], [
                {
                    documentId: document.id,
                    title: document.title || '',
                    type: document.type,
                    path: document.path || '',
                },
            ]);
            this.setCached(cacheKey, chunks, 600000); // 10 minutes TTL
            this.logger.debug(`Chunked document ${document.id} into ${chunks.length} pieces`);
            return chunks;
        }, 'processDocument').catch(async (error) => {
            // Graceful degradation: return simplified chunks
            if (this.degradationLevel !== 'none') {
                const fallback = await this.getFallbackResponse('processDocument', {
                    documentId: document.id,
                });
                if (fallback)
                    return fallback;
                // Create minimal chunks manually
                const content = document.content || '';
                const chunkSize = 1000; // Smaller chunks for degraded mode
                const chunks = [];
                for (let i = 0; i < content.length; i += chunkSize) {
                    const chunk = content.slice(i, i + chunkSize);
                    chunks.push({
                        pageContent: chunk,
                        metadata: {
                            documentId: document.id,
                            title: document.title || '',
                            chunkIndex: Math.floor(i / chunkSize),
                        },
                    });
                }
                this.logger.warn(`Used fallback chunking for document ${document.id}`);
                return chunks;
            }
            throw createError(ErrorCode.ANALYSIS_ERROR, error, `Failed to process document ${document.id}`);
        });
    }
    /**
     * Build or update vector store with documents
     */
    async buildVectorStore(documents) {
        try {
            const allChunks = [];
            for (const doc of documents) {
                const chunks = await this.processDocument(doc);
                allChunks.push(...chunks);
            }
            if (this.vectorStore) {
                // Add to existing store
                await this.vectorStore.addDocuments(allChunks);
            }
            else {
                // Create new store
                this.vectorStore = await LangChainHMSVectorStore.fromDocuments(allChunks, this.embeddings);
            }
            this.logger.info(`Vector store updated with ${allChunks.length} chunks from ${documents.length} documents`);
        }
        catch (error) {
            throw createError(ErrorCode.ANALYSIS_ERROR, error, 'Failed to build vector store');
        }
    }
    /**
     * Perform semantic search across documents
     */
    async semanticSearch(query, topK = 5) {
        if (!this.vectorStore) {
            throw createError(ErrorCode.INVALID_STATE, null, 'Vector store not initialized. Call buildVectorStore first.');
        }
        return this.withCircuitBreaker(async () => {
            const cacheKey = `search_${query.slice(0, 100)}_${topK}`;
            const cached = this.getCached(cacheKey);
            if (cached)
                return cached;
            await this.checkRateLimit(500); // Estimate 500 tokens for embeddings
            // Adjust search quality based on system health
            const adjustedTopK = this.degradationLevel === 'full'
                ? Math.min(topK, 2)
                : this.degradationLevel === 'partial'
                    ? Math.min(topK, 3)
                    : topK;
            const results = (await this.vectorStore?.similaritySearch(query, adjustedTopK)) || [];
            this.setCached(cacheKey, results, 300000); // 5 minutes TTL
            this.logger.debug(`Found ${results.length} similar documents for query: ${query.substring(0, 50)}...`);
            return results;
        }, 'semanticSearch').catch((error) => {
            // Fallback to simple text matching if semantic search fails
            if (this.degradationLevel !== 'none') {
                this.logger.warn('Semantic search failed, using simple text matching fallback');
                return []; // Return empty results as graceful degradation
            }
            throw createError(ErrorCode.ANALYSIS_ERROR, error, 'Semantic search failed');
        });
    }
    /**
     * Generate writing suggestions using RAG
     */
    async generateWithContext(prompt, options = {}) {
        if (!this.vectorStore) {
            throw createError(ErrorCode.INVALID_STATE, null, 'Vector store not initialized');
        }
        return this.withCircuitBreaker(async () => {
            const cacheKey = `generateContext_${prompt.slice(0, 50)}_${JSON.stringify(options)}`;
            const cached = this.getCached(cacheKey);
            if (cached)
                return cached;
            await this.checkRateLimit(2000); // Estimate 2000 tokens
            // Find relevant context
            const relevantDocs = await this.semanticSearch(prompt, options.topK || 3);
            // Build context string
            const context = relevantDocs.map((doc) => doc.pageContent).join('\n\n---\n\n');
            // Adjust prompt complexity based on degradation level
            let promptTemplate;
            if (this.degradationLevel === 'full') {
                promptTemplate = PromptTemplate.fromTemplate(`Answer briefly: {question}`);
            }
            else if (this.degradationLevel === 'partial') {
                promptTemplate = PromptTemplate.fromTemplate(`
					Context: {context}
					Question: {question}
					Provide a concise response:
				`);
            }
            else {
                promptTemplate = PromptTemplate.fromTemplate(`
					You are a professional writing assistant helping with a manuscript.
					Use the following context from the manuscript to provide accurate and consistent suggestions:

					Context:
					{context}

					Question/Request:
					{question}

					Provide a helpful, creative response that maintains consistency with the existing manuscript:
				`);
            }
            // Create chain with adjusted LLM settings
            const adjustedLLM = new ChatOpenAI({
                openAIApiKey: process.env.OPENAI_API_KEY,
                modelName: 'gpt-4-turbo-preview',
                temperature: this.degradationLevel === 'full' ? 0.3 : options.temperature || 0.7,
                maxTokens: this.degradationLevel === 'full' ? 200 : options.maxTokens || 1000,
            });
            const chain = RunnableSequence.from([
                promptTemplate,
                adjustedLLM,
                new StringOutputParser(),
            ]);
            // Generate response
            const response = await chain.invoke({
                context,
                question: prompt,
            });
            this.setCached(cacheKey, response, 180000); // 3 minutes TTL
            return response;
        }, 'generateWithContext').catch(async (error) => {
            // Use fallback response in case of failure
            const fallback = await this.getFallbackResponse('generateWithContext', prompt);
            if (fallback) {
                this.logger.warn('Using fallback response for content generation');
                return fallback;
            }
            throw createError(ErrorCode.AI_SERVICE_ERROR, error, 'Failed to generate with context');
        });
    }
    /**
     * Analyze writing style from samples
     */
    async analyzeWritingStyle(samples) {
        return this.withCircuitBreaker(async () => {
            const cacheKey = `styleAnalysis_${samples.join('').slice(0, 100)}`;
            const cached = this.getCached(cacheKey);
            if (cached)
                return cached;
            await this.checkRateLimit(3000); // Estimate 3000 tokens
            // Adjust analysis depth based on degradation level
            let promptTemplate;
            if (this.degradationLevel === 'full') {
                promptTemplate = PromptTemplate.fromTemplate(`
					Analyze writing style briefly: {samples}
					Return JSON with: tone, complexity, voice.
				`);
            }
            else {
                promptTemplate = PromptTemplate.fromTemplate(`
					Analyze the following writing samples and provide a detailed style analysis:

					Samples:
					{samples}

					Provide analysis of:
					1. Voice and tone
					2. Sentence structure patterns
					3. Vocabulary complexity
					4. Pacing and rhythm
					5. Common phrases or patterns
					6. Strengths and areas for improvement

					Format as JSON.
				`);
            }
            const chain = RunnableSequence.from([
                promptTemplate,
                this.llm,
                new StringOutputParser(),
            ]);
            const response = await chain.invoke({
                samples: samples
                    .slice(0, this.degradationLevel === 'full' ? 2 : samples.length)
                    .join('\n\n---\n\n'),
            });
            try {
                const result = JSON.parse(response);
                this.setCached(cacheKey, result, 900000); // 15 minutes TTL
                return result;
            }
            catch (parseError) {
                // Fallback to basic analysis if JSON parsing fails
                this.logger.warn('Failed to parse style analysis JSON response', {
                    error: parseError.message,
                    response: response.slice(0, 100),
                });
                const basicAnalysis = {
                    tone: 'neutral',
                    complexity: 'medium',
                    voice: 'descriptive',
                    confidence: 0.5,
                    rawResponse: response.slice(0, 200),
                };
                return basicAnalysis;
            }
        }, 'analyzeWritingStyle').catch(async (error) => {
            // Use fallback analysis
            const fallback = await this.getFallbackResponse('analyzeWritingStyle', samples);
            if (fallback) {
                this.logger.warn('Using fallback writing style analysis');
                return fallback;
            }
            throw createError(ErrorCode.AI_SERVICE_ERROR, error, 'Style analysis failed');
        });
    }
    /**
     * Generate chapter summaries
     */
    async summarizeChapter(content, maxLength = 200) {
        return this.withCircuitBreaker(async () => {
            const cacheKey = `summary_${content.slice(0, 50)}_${maxLength}`;
            const cached = this.getCached(cacheKey);
            if (cached)
                return cached;
            await this.checkRateLimit(1500); // Estimate 1500 tokens
            // Adjust summary complexity based on degradation
            const adjustedLength = this.degradationLevel === 'full'
                ? Math.min(maxLength, 50)
                : this.degradationLevel === 'partial'
                    ? Math.min(maxLength, 100)
                    : maxLength;
            const truncatedContent = this.degradationLevel === 'full' ? content.slice(0, 500) : content;
            const promptTemplate = PromptTemplate.fromTemplate(`
				Summarize the following chapter content in approximately {maxLength} words:

				{content}

				Focus on key plot points, character developments, and important themes.
			`);
            const chain = RunnableSequence.from([
                promptTemplate,
                this.llm,
                new StringOutputParser(),
            ]);
            const summary = await chain.invoke({
                content: truncatedContent,
                maxLength: adjustedLength,
            });
            this.setCached(cacheKey, summary, 1200000); // 20 minutes TTL
            return summary;
        }, 'summarizeChapter').catch(async (error) => {
            // Fallback to extractive summary
            const fallback = await this.getFallbackResponse('summarizeChapter', content);
            if (fallback) {
                return fallback;
            }
            // Create basic extractive summary as last resort
            if (this.degradationLevel !== 'none') {
                const sentences = content.split(/[.!?]\s+/).filter((s) => s.length > 20);
                const importantSentences = sentences.slice(0, Math.ceil(maxLength / 20));
                this.logger.warn('Using extractive summary fallback');
                return `${importantSentences.join('. ')}.`;
            }
            throw createError(ErrorCode.AI_SERVICE_ERROR, error, 'Chapter summarization failed');
        });
    }
    /**
     * Check plot consistency across documents
     */
    async checkPlotConsistency(documents) {
        if (!this.vectorStore) {
            await this.buildVectorStore(documents);
        }
        try {
            const issues = [];
            // Check for character consistency
            const characterPrompt = `
				Analyze the manuscript for character inconsistencies:
				- Changes in character traits or behavior
				- Contradictory character descriptions
				- Timeline issues with character ages or events
			`;
            const characterContext = await this.generateWithContext(characterPrompt);
            // Parse and structure the response with sophisticated analysis
            const lines = characterContext.split('\n').filter((line) => line.trim());
            for (const line of lines) {
                // Look for specific patterns indicating issues
                const severityPatterns = {
                    high: /critical|major|severe|significant/i,
                    medium: /moderate|notable|important/i,
                    low: /minor|small|slight/i,
                };
                const issuePatterns = [
                    /inconsistency|contradiction|conflict/i,
                    /timeline\s+issue|age\s+problem/i,
                    /character\s+trait\s+change/i,
                    /behavior\s+mismatch/i,
                ];
                // Check if this line describes an issue
                const hasIssue = issuePatterns.some((pattern) => pattern.test(line));
                if (hasIssue) {
                    // Determine severity
                    let severity = 'medium';
                    for (const [level, pattern] of Object.entries(severityPatterns)) {
                        if (pattern.test(line)) {
                            severity = level;
                            break;
                        }
                    }
                    // Extract chapter references if mentioned
                    const chapterMatch = line.match(/chapter[s]?\s+(\d+(?:\s*(?:and|,)\s*\d+)*)/gi);
                    const locations = chapterMatch
                        ? chapterMatch.map((m) => m.replace(/chapter[s]?\s+/i, 'Chapter '))
                        : ['Multiple chapters'];
                    // Generate contextual suggestion
                    let suggestion = 'Review character descriptions for consistency';
                    if (line.toLowerCase().includes('timeline')) {
                        suggestion = 'Verify character ages and timeline events';
                    }
                    else if (line.toLowerCase().includes('trait')) {
                        suggestion =
                            'Ensure character traits remain consistent or show clear development';
                    }
                    else if (line.toLowerCase().includes('behavior')) {
                        suggestion =
                            'Check that character behaviors align with established personality';
                    }
                    issues.push({
                        issue: line.trim(),
                        severity,
                        locations,
                        suggestion,
                    });
                }
            }
            return issues;
        }
        catch (error) {
            throw createError(ErrorCode.AI_SERVICE_ERROR, error, 'Plot consistency check failed');
        }
    }
    /**
     * Get comprehensive service health status
     */
    getHealthStatus() {
        const avgLatency = this.performanceMetrics.latency.length > 0
            ? this.performanceMetrics.latency.reduce((a, b) => a + b, 0) /
                this.performanceMetrics.latency.length
            : 0;
        const recentFailures = this.predictionModel.failures.filter((f) => Date.now() - f < 300000);
        const recentSuccesses = this.predictionModel.successes.filter((s) => Date.now() - s < 300000);
        const total = recentFailures.length + recentSuccesses.length;
        const errorRate = total > 0 ? recentFailures.length / total : 0;
        const successRate = total > 0 ? recentSuccesses.length / total : 1;
        const cacheEntries = Array.from(this.intelligentCache.values());
        const totalHits = cacheEntries.reduce((sum, entry) => sum + entry.hitCount, 0);
        const hitRate = cacheEntries.length > 0 ? totalHits / cacheEntries.length : 0;
        return {
            healthScore: this.healthScore,
            circuitBreakerState: this.circuitBreaker.state,
            degradationLevel: this.degradationLevel,
            performanceMetrics: {
                avgLatency,
                errorRate,
                successRate,
            },
            failureRisk: this.predictFailureRisk(),
            cacheStats: {
                size: this.intelligentCache.size,
                hitRate,
            },
        };
    }
    /**
     * Force reset service to healthy state (for emergency recovery)
     */
    resetToHealthyState() {
        this.circuitBreaker = {
            state: 'CLOSED',
            failureCount: 0,
            lastFailureTime: 0,
            firstFailureTime: 0,
            successCount: 0,
            nextAttemptTime: 0,
        };
        this.healthScore = 1.0;
        this.degradationLevel = 'none';
        this.rateLimiter.successRate = 1.0;
        this.predictionModel = { failures: [], successes: [], patterns: [] };
        this.performanceMetrics = {
            latency: [],
            throughput: 0,
            errorRate: 0,
            lastUpdated: Date.now(),
        };
        this.logger.info('Service reset to healthy state');
    }
    /**
     * Clear vector store and contexts
     */
    clearMemory() {
        this.vectorStore = null;
        this.contexts.clear();
        this.intelligentCache.clear();
        this.logger.debug('Cleared vector store, contexts, and cache');
    }
}
//# sourceMappingURL=langchain-service.js.map