"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AITaskService = void 0;
const tsyringe_1 = require("tsyringe");
const openai_service_1 = require("./openai.service");
const langchain_service_1 = require("./langchain.service");
const nlp_service_1 = require("../nlp/nlp.service");
const task_ai_context_entity_1 = require("../../entities/task-ai-context.entity");
const typeorm_1 = require("typeorm");
const ioredis_1 = require("ioredis");
const rate_limiter_flexible_1 = require("rate-limiter-flexible");
const ai_config_1 = require("../../config/ai.config");
/**
 * Cache key generators
 */
const CACHE_KEYS = {
    taskSuggestion: (projectId) => `ai:suggestions:${projectId}`,
    taskBreakdown: (taskId) => `ai:breakdown:${taskId}`,
    nlpTask: (promptHash) => `ai:nlp:${promptHash}`,
};
/**
 * Service for AI-powered task management
 * Integrates OpenAI and LangChain for intelligent task operations
 */
let AITaskService = class AITaskService {
    constructor(openAIService, langChainService, nlpService, redis) {
        this.openAIService = openAIService;
        this.langChainService = langChainService;
        this.nlpService = nlpService;
        this.aiContextRepository = (0, typeorm_1.getRepository)(task_ai_context_entity_1.TaskAIContextEntity);
        this.config = (0, ai_config_1.getAIConfig)();
        this.redis = redis;
        this.initializeRateLimiter();
    }
    /**
     * Initialize rate limiter for API calls
     */
    initializeRateLimiter() {
        if (this.redis) {
            this.rateLimiter = new rate_limiter_flexible_1.RateLimiterRedis({
                storeClient: this.redis,
                keyPrefix: 'ai_rate_limit',
                points: this.config.rateLimit.maxRequestsPerMinute,
                duration: 60, // 1 minute
                blockDuration: 10, // Block for 10 seconds
            });
        }
    }
    /**
     * Create a task from natural language input
     * @param request Natural language task request
     * @returns AI-generated task
     */
    async createTaskFromNaturalLanguage(request) {
        // Check rate limit
        await this.checkRateLimit('nlp_task_creation');
        // Check cache
        const cacheKey = CACHE_KEYS.nlpTask(this.hashString(request.prompt));
        const cached = await this.getCached(cacheKey);
        if (cached) {
            return cached;
        }
        const startTime = Date.now();
        try {
            // First, process through NLP pipeline for structured extraction
            const nlpResult = await this.nlpService.processTextWithTranslation({
                text: request.prompt,
                context: request.context,
                projectId: request.projectId,
                userId: request.context?.userRole,
            });
            // Build initial task from NLP extraction
            let task;
            if (nlpResult.success && nlpResult.structuredData) {
                // Use NLP-extracted data as base
                task = {
                    title: nlpResult.entities.title || nlpResult.structuredData.title || '',
                    description: nlpResult.entities.description || nlpResult.structuredData.description || request.prompt,
                    priority: nlpResult.entities.priority || 'medium',
                    complexity: this.mapComplexityFromNLP(nlpResult.classification.complexity),
                    estimatedHours: nlpResult.entities.estimatedHours,
                    acceptanceCriteria: nlpResult.entities.acceptanceCriteria || [],
                    tags: nlpResult.entities.tags || [],
                    dependencies: nlpResult.entities.dependencies || [],
                    assignee: nlpResult.entities.assignees?.[0],
                    subtasks: [],
                };
                // If NLP extraction was incomplete, enhance with LangChain
                if (!task.title || !task.description || task.acceptanceCriteria.length === 0) {
                    const projectContext = this.buildProjectContext(request.context);
                    const langChainTask = await this.langChainService.createTaskFromNaturalLanguage(request.prompt, projectContext, { temperature: 0.5 });
                    // Merge LangChain results with NLP results
                    task = {
                        ...langChainTask,
                        // Prefer NLP-extracted values when available
                        priority: nlpResult.entities.priority || langChainTask.priority,
                        estimatedHours: nlpResult.entities.estimatedHours || langChainTask.estimatedHours,
                        tags: [...new Set([...(nlpResult.entities.tags || []), ...(langChainTask.tags || [])])],
                        dependencies: [...new Set([...(nlpResult.entities.dependencies || []), ...(langChainTask.dependencies || [])])],
                    };
                }
            }
            else {
                // Fallback to LangChain if NLP fails
                const projectContext = this.buildProjectContext(request.context);
                task = await this.langChainService.createTaskFromNaturalLanguage(request.prompt, projectContext, { temperature: 0.5 });
            }
            // Enhance with OpenAI if needed for additional details
            if (!task.acceptanceCriteria || task.acceptanceCriteria.length === 0) {
                const criteriaPrompt = `Generate 3-5 acceptance criteria for this task: ${task.title}\nDescription: ${task.description}`;
                const criteriaResponse = await this.openAIService.complete([{ role: 'user', content: criteriaPrompt }], {
                    systemPrompt: 'You are a technical project manager. Generate clear, testable acceptance criteria.',
                    temperature: 0.3,
                    maxTokens: 500,
                });
                task.acceptanceCriteria = criteriaResponse.content
                    .split('\n')
                    .filter(line => line.trim())
                    .map(line => line.replace(/^[\d\-\*\.]+\s*/, '').trim());
            }
            // Store AI context for auditing
            await this.saveAIContext({
                task_id: undefined, // Will be linked when task is created
                ai_agent_type: nlpResult?.success ? task_ai_context_entity_1.AIAgentType.NLP : task_ai_context_entity_1.AIAgentType.LANGCHAIN,
                context_type: task_ai_context_entity_1.AIContextType.PROMPT,
                context_data: {
                    prompt: request.prompt,
                    context: request.context,
                    model: this.config.openai.model,
                    nlpResult: nlpResult ? {
                        success: nlpResult.success,
                        language: nlpResult.language,
                        confidence: nlpResult.entities.confidence,
                        tokensUsed: nlpResult.tokensUsed,
                    } : undefined,
                },
                execution_timestamp: new Date(),
                success: true,
                tokens_used: (nlpResult?.tokensUsed || 0) + (task.executionTimeMs ? Math.ceil(task.executionTimeMs / 10) : 0),
                execution_time_ms: Date.now() - startTime,
            });
            // Cache the result
            await this.setCached(cacheKey, task, 3600); // 1 hour TTL
            return task;
        }
        catch (error) {
            // Log error context
            await this.saveAIContext({
                task_id: undefined,
                ai_agent_type: task_ai_context_entity_1.AIAgentType.LANGCHAIN,
                context_type: task_ai_context_entity_1.AIContextType.ERROR,
                context_data: {
                    prompt: request.prompt,
                    error: error instanceof Error ? error.message : 'Unknown error',
                },
                execution_timestamp: new Date(),
                success: false,
                tokens_used: 0,
                execution_time_ms: Date.now() - startTime,
            });
            throw error;
        }
    }
    /**
     * Break down a complex task into subtasks
     * @param taskId Task ID to break down
     * @param taskDescription Task description
     * @param context Additional context
     * @returns Task breakdown with subtasks
     */
    async breakdownTask(taskId, taskDescription, context) {
        // Check rate limit
        await this.checkRateLimit('task_breakdown');
        // Check cache
        const cacheKey = CACHE_KEYS.taskBreakdown(taskId);
        const cached = await this.getCached(cacheKey);
        if (cached) {
            return cached;
        }
        const startTime = Date.now();
        try {
            const breakdown = await this.langChainService.breakdownTask(taskDescription, context || '', { temperature: 0.3, maxTokens: 2000 });
            // Store AI context
            await this.saveAIContext({
                task_id: taskId,
                ai_agent_type: task_ai_context_entity_1.AIAgentType.LANGCHAIN,
                context_type: task_ai_context_entity_1.AIContextType.RESULT,
                context_data: {
                    operation: 'task_breakdown',
                    result: breakdown,
                },
                execution_timestamp: new Date(),
                success: true,
                tokens_used: breakdown.estimatedTotalHours ? breakdown.estimatedTotalHours * 10 : 100,
                execution_time_ms: Date.now() - startTime,
            });
            // Cache the result
            await this.setCached(cacheKey, breakdown, 7200); // 2 hour TTL
            return breakdown;
        }
        catch (error) {
            await this.saveAIContext({
                task_id: taskId,
                ai_agent_type: task_ai_context_entity_1.AIAgentType.LANGCHAIN,
                context_type: task_ai_context_entity_1.AIContextType.ERROR,
                context_data: {
                    operation: 'task_breakdown',
                    error: error instanceof Error ? error.message : 'Unknown error',
                },
                execution_timestamp: new Date(),
                success: false,
                tokens_used: 0,
                execution_time_ms: Date.now() - startTime,
            });
            throw error;
        }
    }
    /**
     * Get AI-powered task suggestions for a project
     * @param projectId Project ID
     * @param currentContext Current project context
     * @returns List of task suggestions
     */
    async getTaskSuggestions(projectId, currentContext) {
        // Check rate limit
        await this.checkRateLimit('task_suggestions');
        // Check cache
        const cacheKey = CACHE_KEYS.taskSuggestion(projectId);
        const cached = await this.getCached(cacheKey);
        if (cached) {
            return cached;
        }
        const startTime = Date.now();
        try {
            const suggestionsText = await this.langChainService.getTaskSuggestions(currentContext, { temperature: 0.7, maxTokens: 1500 });
            // Parse suggestions into structured format
            const suggestions = this.parseSuggestions(suggestionsText);
            // Store AI context
            await this.saveAIContext({
                task_id: undefined,
                ai_agent_type: task_ai_context_entity_1.AIAgentType.LANGCHAIN,
                context_type: task_ai_context_entity_1.AIContextType.RESULT,
                context_data: {
                    operation: 'task_suggestions',
                    projectId,
                    suggestions,
                },
                execution_timestamp: new Date(),
                success: true,
                tokens_used: Math.ceil(suggestionsText.length / 4),
                execution_time_ms: Date.now() - startTime,
            });
            // Cache suggestions
            await this.setCached(cacheKey, suggestions, 1800); // 30 minutes TTL
            return suggestions;
        }
        catch (error) {
            await this.saveAIContext({
                task_id: undefined,
                ai_agent_type: task_ai_context_entity_1.AIAgentType.LANGCHAIN,
                context_type: task_ai_context_entity_1.AIContextType.ERROR,
                context_data: {
                    operation: 'task_suggestions',
                    projectId,
                    error: error instanceof Error ? error.message : 'Unknown error',
                },
                execution_timestamp: new Date(),
                success: false,
                tokens_used: 0,
                execution_time_ms: Date.now() - startTime,
            });
            throw error;
        }
    }
    /**
     * Update AI context for a task based on feedback
     * @param taskId Task ID
     * @param feedback User feedback
     */
    async updateTaskContext(taskId, feedback) {
        const startTime = Date.now();
        await this.saveAIContext({
            task_id: taskId,
            ai_agent_type: task_ai_context_entity_1.AIAgentType.OPENAI,
            context_type: task_ai_context_entity_1.AIContextType.FEEDBACK,
            context_data: {
                feedback,
                timestamp: new Date().toISOString(),
            },
            execution_timestamp: new Date(),
            success: true,
            tokens_used: 0,
            execution_time_ms: Date.now() - startTime,
        });
        // Invalidate related caches
        await this.invalidateCache(`ai:breakdown:${taskId}`);
    }
    /**
     * Warm up cache with common queries
     * @param projectId Project ID to warm cache for
     */
    async warmCache(projectId) {
        // This method can be called during low-traffic periods
        // to pre-populate cache with likely queries
        const commonPrompts = [
            'Create a bug fix task',
            'Add a new feature',
            'Improve performance',
            'Write documentation',
        ];
        for (const prompt of commonPrompts) {
            try {
                await this.createTaskFromNaturalLanguage({
                    prompt,
                    projectId,
                });
            }
            catch (error) {
                console.warn(`Cache warming failed for prompt: ${prompt}`, error);
            }
        }
    }
    /**
     * Get AI service health status
     * @returns Health status of AI services
     */
    async getHealthStatus() {
        const [openaiHealth, langchainHealth] = await Promise.all([
            this.openAIService.testConnection(),
            this.langChainService.testConnection(),
        ]);
        return {
            openai: openaiHealth,
            langchain: langchainHealth,
            redis: this.redis ? await this.testRedisConnection() : false,
            rateLimiter: !!this.rateLimiter,
        };
    }
    /**
     * Get usage statistics for AI services
     * @returns Combined usage stats
     */
    async getUsageStats() {
        const openaiStats = this.openAIService.getUsageStats();
        const contextCount = await this.aiContextRepository.count();
        return {
            openai: openaiStats,
            totalContextsSaved: contextCount,
            cacheHitRate: this.redis ? await this.calculateCacheHitRate() : undefined,
        };
    }
    /**
     * Process task update with NLP understanding
     * @param taskId Task to update
     * @param updatePrompt Natural language update description
     * @returns Updated task information
     */
    async processTaskUpdate(taskId, updatePrompt) {
        // Process update through NLP
        const nlpResult = await this.nlpService.processText({
            text: updatePrompt,
            context: { taskId, operation: 'update' },
        });
        const updates = {};
        // Extract specific updates from NLP results
        if (nlpResult.entities.priority) {
            updates.priority = nlpResult.entities.priority;
        }
        if (nlpResult.entities.estimatedHours !== undefined) {
            updates.estimatedHours = nlpResult.entities.estimatedHours;
        }
        if (nlpResult.entities.assignees?.length) {
            updates.assignee = nlpResult.entities.assignees[0];
        }
        if (nlpResult.entities.tags?.length) {
            updates.tags = nlpResult.entities.tags;
        }
        if (nlpResult.entities.acceptanceCriteria?.length) {
            updates.acceptanceCriteria = nlpResult.entities.acceptanceCriteria;
        }
        // Store context for auditing
        await this.saveAIContext({
            task_id: taskId,
            ai_agent_type: task_ai_context_entity_1.AIAgentType.NLP,
            context_type: task_ai_context_entity_1.AIContextType.UPDATE,
            context_data: {
                updatePrompt,
                extractedUpdates: updates,
                nlpConfidence: nlpResult.entities.confidence,
            },
            execution_timestamp: new Date(),
            success: true,
            tokens_used: nlpResult.tokensUsed || 0,
            execution_time_ms: nlpResult.processingTime,
        });
        return updates;
    }
    /**
     * Get task recommendations based on NLP analysis
     * @param projectId Project to analyze
     * @param recentActivity Recent project activity for context
     * @returns Task suggestions
     */
    async getTaskRecommendations(projectId, recentActivity) {
        // Check cache
        const cacheKey = CACHE_KEYS.taskSuggestion(projectId);
        const cached = await this.getCached(cacheKey);
        if (cached) {
            return cached;
        }
        // Build context from recent activity
        const activityContext = recentActivity?.join('\n') || '';
        // Use LangChain to generate intelligent suggestions
        const suggestions = await this.langChainService.suggestNextTasks(projectId, activityContext, { temperature: 0.7, maxTokens: 1500 });
        // Process each suggestion through NLP for enhanced extraction
        const enhancedSuggestions = [];
        for (const suggestion of suggestions) {
            const nlpResult = await this.nlpService.processText({
                text: `${suggestion.title}: ${suggestion.description}`,
                projectId,
            });
            enhancedSuggestions.push({
                ...suggestion,
                priority: nlpResult.entities.priority || suggestion.priority || 'medium',
                estimatedHours: nlpResult.entities.estimatedHours || suggestion.estimatedHours,
                confidence: (suggestion.confidence + (nlpResult.entities.confidence || 0)) / 2,
            });
        }
        // Cache results
        await this.setCached(cacheKey, enhancedSuggestions, 1800); // 30 minutes
        return enhancedSuggestions;
    }
    /**
     * Validate task description using NLP
     * @param description Task description to validate
     * @returns Validation result with suggestions
     */
    async validateTaskDescription(description) {
        const nlpResult = await this.nlpService.processText({
            text: description,
        });
        const issues = [];
        const suggestions = [];
        // Check for completeness
        if (!nlpResult.entities.title && !nlpResult.structuredData?.title) {
            issues.push('No clear task title found');
            suggestions.push('Start with a clear, action-oriented title');
        }
        if (!nlpResult.entities.acceptanceCriteria?.length) {
            issues.push('No acceptance criteria specified');
            suggestions.push('Add clear, testable acceptance criteria');
        }
        if (!nlpResult.entities.estimatedHours) {
            suggestions.push('Consider adding a time estimate');
        }
        if (!nlpResult.entities.priority) {
            suggestions.push('Specify the task priority (low, medium, high, critical)');
        }
        // Check confidence
        if (nlpResult.entities.confidence && nlpResult.entities.confidence < 0.6) {
            issues.push('Description is ambiguous or unclear');
            suggestions.push('Provide more specific details about the task');
        }
        return {
            isValid: issues.length === 0,
            issues,
            suggestions,
            extractedInfo: {
                entities: nlpResult.entities,
                classification: nlpResult.classification,
            },
        };
    }
    // Helper methods
    /**
     * Map NLP complexity to task complexity
     */
    mapComplexityFromNLP(nlpComplexity) {
        switch (nlpComplexity) {
            case 'trivial':
            case 'simple':
                return 'trivial';
            case 'moderate':
                return 'minor';
            case 'complex':
                return 'major';
            case 'very-complex':
                return 'critical';
            default:
                return 'minor';
        }
    }
    buildProjectContext(context) {
        if (!context)
            return '';
        const parts = [];
        if (context.projectDescription) {
            parts.push(`Project: ${context.projectDescription}`);
        }
        if (context.currentTasks?.length) {
            parts.push(`Current tasks: ${context.currentTasks.join(', ')}`);
        }
        if (context.completedTasks?.length) {
            parts.push(`Completed tasks: ${context.completedTasks.join(', ')}`);
        }
        if (context.userRole) {
            parts.push(`User role: ${context.userRole}`);
        }
        return parts.join('\n');
    }
    parseSuggestions(text) {
        const suggestions = [];
        const lines = text.split('\n');
        let currentSuggestion = null;
        for (const line of lines) {
            const trimmed = line.trim();
            // Match numbered items (1. Title or 1) Title)
            const match = trimmed.match(/^(\d+)[.\)]\s*(.+)/);
            if (match) {
                if (currentSuggestion) {
                    suggestions.push(this.finalizeSuggestion(currentSuggestion));
                }
                const [, number, titleAndDesc] = match;
                const [title, ...descParts] = titleAndDesc.split('-').map(s => s.trim());
                currentSuggestion = {
                    id: `suggestion-${number}`,
                    title,
                    description: descParts.join(' - '),
                    rationale: '',
                    priority: 'medium',
                    confidence: 0.7,
                };
            }
            else if (currentSuggestion && trimmed) {
                // Add to description or rationale
                if (trimmed.toLowerCase().includes('because') ||
                    trimmed.toLowerCase().includes('rationale')) {
                    currentSuggestion.rationale = trimmed;
                }
                else {
                    currentSuggestion.description =
                        `${currentSuggestion.description} ${trimmed}`.trim();
                }
            }
        }
        if (currentSuggestion) {
            suggestions.push(this.finalizeSuggestion(currentSuggestion));
        }
        return suggestions;
    }
    finalizeSuggestion(partial) {
        return {
            id: partial.id || `suggestion-${Date.now()}`,
            title: partial.title || 'Untitled Task',
            description: partial.description || '',
            rationale: partial.rationale || 'AI-generated suggestion based on project context',
            priority: partial.priority || 'medium',
            estimatedHours: partial.estimatedHours,
            confidence: partial.confidence || 0.5,
        };
    }
    async saveAIContext(context) {
        try {
            const aiContext = this.aiContextRepository.create(context);
            await this.aiContextRepository.save(aiContext);
        }
        catch (error) {
            console.error('Failed to save AI context:', error);
            // Don't throw - this is auxiliary functionality
        }
    }
    async checkRateLimit(operation) {
        if (!this.rateLimiter)
            return;
        try {
            await this.rateLimiter.consume(operation);
        }
        catch (error) {
            throw new Error(`Rate limit exceeded for ${operation}. Please try again later.`);
        }
    }
    async getCached(key) {
        if (!this.redis)
            return null;
        try {
            const cached = await this.redis.get(key);
            return cached ? JSON.parse(cached) : null;
        }
        catch (error) {
            console.warn('Cache get failed:', error);
            return null;
        }
    }
    async setCached(key, value, ttl) {
        if (!this.redis)
            return;
        try {
            await this.redis.set(key, JSON.stringify(value), 'EX', ttl);
        }
        catch (error) {
            console.warn('Cache set failed:', error);
        }
    }
    async invalidateCache(pattern) {
        if (!this.redis)
            return;
        try {
            const keys = await this.redis.keys(pattern);
            if (keys.length > 0) {
                await this.redis.del(...keys);
            }
        }
        catch (error) {
            console.warn('Cache invalidation failed:', error);
        }
    }
    hashString(str) {
        // Simple hash for cache keys
        let hash = 0;
        for (let i = 0; i < str.length; i++) {
            const char = str.charCodeAt(i);
            hash = ((hash << 5) - hash) + char;
            hash = hash & hash; // Convert to 32-bit integer
        }
        return Math.abs(hash).toString(36);
    }
    async testRedisConnection() {
        if (!this.redis)
            return false;
        try {
            await this.redis.ping();
            return true;
        }
        catch {
            return false;
        }
    }
    async calculateCacheHitRate() {
        // This would typically track hits/misses over time
        // For now, return a placeholder
        return 0.0;
    }
};
exports.AITaskService = AITaskService;
exports.AITaskService = AITaskService = __decorate([
    (0, tsyringe_1.injectable)(),
    __param(0, (0, tsyringe_1.inject)('OpenAIService')),
    __param(1, (0, tsyringe_1.inject)('LangChainService')),
    __param(2, (0, tsyringe_1.inject)('NLPService')),
    __param(3, (0, tsyringe_1.inject)('Redis')),
    __metadata("design:paramtypes", [openai_service_1.OpenAIService,
        langchain_service_1.LangChainService,
        nlp_service_1.NLPService,
        ioredis_1.Redis])
], AITaskService);
//# sourceMappingURL=ai-task.service.js.map