"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);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EntityExtractorService = void 0;
const tsyringe_1 = require("tsyringe");
const luxon_1 = require("luxon");
const openai_service_1 = require("../../ai/openai.service");
const translation_service_1 = require("../translation.service");
const language_detector_service_1 = require("../language-detector.service");
/**
 * Patterns for entity extraction
 */
const PRIORITY_PATTERNS = {
    critical: /\b(critical|urgent|asap|immediately|emergency|blocker)\b/i,
    high: /\b(high priority|important|soon|quickly|fast)\b/i,
    medium: /\b(medium priority|normal|standard|regular)\b/i,
    low: /\b(low priority|minor|nice to have|whenever|eventually)\b/i,
};
const TIME_ESTIMATE_PATTERNS = {
    hours: /\b(\d+(?:\.\d+)?)\s*(?:hours?|hrs?)\b/i,
    days: /\b(\d+(?:\.\d+)?)\s*(?:days?|d)\b/i,
    weeks: /\b(\d+(?:\.\d+)?)\s*(?:weeks?|wks?)\b/i,
    points: /\b(\d+(?:\.\d+)?)\s*(?:story\s*)?points?\b/i,
};
const DEPENDENCY_PATTERNS = [
    /\b(?:depends on|blocked by|requires|needs|after)\s+(?:task\s+)?#?(\d+)\b/i,
    /\b(?:related to|see also|ref)\s+(?:task\s+)?#?(\d+)\b/i,
];
/**
 * Entity Extractor Service for NLP
 */
let EntityExtractorService = class EntityExtractorService {
    constructor(openAIService, translationService, languageDetector) {
        this.openAIService = openAIService;
        this.translationService = translationService;
        this.languageDetector = languageDetector;
    }
    /**
     * Extract all entities from parsed text
     */
    async extract(input) {
        const { text, parsedData, language, context } = input;
        // Run all extractions in parallel for efficiency
        const [titleAndDescription, priority, deadline, estimatedHours, assignees, dependencies, technologies, acceptanceCriteria,] = await Promise.all([
            this.extractTitleAndDescription(text, parsedData),
            this.extractPriority(text),
            this.extractDeadline(text, language),
            this.extractTimeEstimate(text),
            this.extractAssignees(text, context),
            this.extractDependencies(text),
            this.extractTechnologies(text),
            this.extractAcceptanceCriteria(text),
        ]);
        // Extract tags from keywords and other entities
        const tags = this.generateTags(parsedData.keywords, technologies, priority);
        // Calculate confidence score
        const confidence = this.calculateConfidence({
            hasTitle: !!titleAndDescription.title,
            hasDescription: !!titleAndDescription.description,
            hasPriority: !!priority,
            hasDeadline: !!deadline,
            hasEstimate: !!estimatedHours,
        });
        return {
            ...titleAndDescription,
            priority,
            deadline,
            estimatedHours,
            assignees,
            dependencies,
            tags,
            acceptanceCriteria,
            mentions: assignees, // For now, mentions are same as assignees
            technologies,
            confidence,
        };
    }
    /**
     * Extract title and description using AI
     */
    async extractTitleAndDescription(text, parsedData) {
        try {
            const prompt = `Extract a concise task title and detailed description from this text. Return JSON with "title" (max 100 chars) and "description" fields.

Text: "${text}"

Return only JSON, no additional text.`;
            const result = await this.openAIService.complete([{ role: 'user', content: prompt }], { maxTokens: 200, temperature: 0.3 });
            try {
                const parsed = JSON.parse(result.content.trim());
                return {
                    title: parsed.title?.substring(0, 100),
                    description: parsed.description || text,
                };
            }
            catch {
                // Fallback to first sentence as title
                return {
                    title: parsedData.sentences[0]?.text.substring(0, 100) || text.substring(0, 100),
                    description: text,
                };
            }
        }
        catch (error) {
            console.error('Title/description extraction failed:', error);
            return {
                title: text.substring(0, 100),
                description: text,
            };
        }
    }
    /**
     * Extract priority level from text
     */
    extractPriority(text) {
        for (const [level, pattern] of Object.entries(PRIORITY_PATTERNS)) {
            if (pattern.test(text)) {
                return level;
            }
        }
        // Check for exclamation marks as urgency indicator
        const exclamationCount = (text.match(/!/g) || []).length;
        if (exclamationCount >= 3)
            return 'critical';
        if (exclamationCount >= 2)
            return 'high';
        return 'medium'; // Default priority
    }
    /**
     * Extract deadline from text using Luxon and locale-aware parsing
     */
    async extractDeadline(text, language) {
        // First try locale-specific date parsing if not English
        if (language !== 'en') {
            const localeDate = this.translationService.parseLocaleDate(text, language);
            if (localeDate) {
                return localeDate;
            }
        }
        // Common date patterns
        const patterns = [
            /\b(?:by|before|due|deadline|until)\s+(.+?)(?:\.|,|;|$)/i,
            /\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b/,
            /\b(\d{4}[/-]\d{1,2}[/-]\d{1,2})\b/,
            /\b(tomorrow|today|next\s+(?:week|month|monday|tuesday|wednesday|thursday|friday))\b/i,
        ];
        for (const pattern of patterns) {
            const match = text.match(pattern);
            if (match && match[1]) {
                const dateStr = match[1].trim();
                // Try parsing with Luxon
                const parsed = this.parseDate(dateStr, language);
                if (parsed)
                    return parsed;
            }
        }
        // Use AI for complex date extraction
        try {
            const prompt = `Extract any deadline or due date from this text. Return the date in ISO format (YYYY-MM-DD) or null if no date found.

Text: "${text}"

Return only the date or null, no additional text.`;
            const result = await this.openAIService.complete([{ role: 'user', content: prompt }], { maxTokens: 50, temperature: 0.1 });
            const dateStr = result.content.trim();
            if (dateStr && dateStr !== 'null') {
                const dt = luxon_1.DateTime.fromISO(dateStr);
                if (dt.isValid) {
                    return dt.toJSDate();
                }
            }
        }
        catch (error) {
            console.error('AI deadline extraction failed:', error);
        }
        return undefined;
    }
    /**
     * Parse date string using Luxon
     */
    parseDate(dateStr, language) {
        const now = luxon_1.DateTime.now();
        const lower = dateStr.toLowerCase();
        // Handle relative dates
        if (lower === 'today')
            return now.toJSDate();
        if (lower === 'tomorrow')
            return now.plus({ days: 1 }).toJSDate();
        if (lower === 'next week')
            return now.plus({ weeks: 1 }).toJSDate();
        if (lower === 'next month')
            return now.plus({ months: 1 }).toJSDate();
        // Handle day names
        const dayNames = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
        const dayIndex = dayNames.findIndex(day => lower.includes(day));
        if (dayIndex >= 0) {
            const targetDay = (dayIndex + 1); // Luxon WeekdayNumbers type
            let target = now.set({ weekday: targetDay });
            if (target <= now) {
                target = target.plus({ weeks: 1 });
            }
            return target.toJSDate();
        }
        // Try various date formats
        const formats = [
            'MM/dd/yyyy',
            'dd/MM/yyyy',
            'yyyy-MM-dd',
            'MMM dd, yyyy',
            'dd MMM yyyy',
        ];
        for (const format of formats) {
            const dt = luxon_1.DateTime.fromFormat(dateStr, format);
            if (dt.isValid) {
                return dt.toJSDate();
            }
        }
        // Try ISO format
        const dt = luxon_1.DateTime.fromISO(dateStr);
        if (dt.isValid) {
            return dt.toJSDate();
        }
        return undefined;
    }
    /**
     * Extract time estimate from text
     */
    extractTimeEstimate(text) {
        for (const [unit, pattern] of Object.entries(TIME_ESTIMATE_PATTERNS)) {
            const match = text.match(pattern);
            if (match && match[1]) {
                const value = parseFloat(match[1]);
                // Convert to hours
                switch (unit) {
                    case 'hours':
                        return value;
                    case 'days':
                        return value * 8; // Assuming 8-hour workday
                    case 'weeks':
                        return value * 40; // Assuming 40-hour workweek
                    case 'points':
                        return value * 4; // Rough conversion: 1 point ≈ 4 hours
                    default:
                        return value;
                }
            }
        }
        return undefined;
    }
    /**
     * Extract assignees and mentions using NER
     */
    async extractAssignees(text, context) {
        const assignees = new Set();
        // Check for @mentions
        const mentions = text.match(/@(\w+)/g);
        if (mentions) {
            mentions.forEach(mention => {
                assignees.add(mention.substring(1));
            });
        }
        // Check context for known users
        if (context?.users && Array.isArray(context.users)) {
            context.users.forEach((user) => {
                if (text.toLowerCase().includes(user.toLowerCase())) {
                    assignees.add(user);
                }
            });
        }
        // Use AI for name extraction if no assignees found
        if (assignees.size === 0) {
            try {
                const prompt = `Extract any person names or assignees mentioned in this text. Return as JSON array of names.

Text: "${text}"

Return only the JSON array, no additional text. Return empty array if no names found.`;
                const result = await this.openAIService.complete([{ role: 'user', content: prompt }], { maxTokens: 100, temperature: 0.3 });
                try {
                    const names = JSON.parse(result.content.trim());
                    if (Array.isArray(names)) {
                        names.forEach(name => assignees.add(name));
                    }
                }
                catch {
                    // Ignore parse errors
                }
            }
            catch (error) {
                console.error('AI assignee extraction failed:', error);
            }
        }
        return Array.from(assignees);
    }
    /**
     * Extract task dependencies and blockers
     */
    extractDependencies(text) {
        const dependencies = new Set();
        for (const pattern of DEPENDENCY_PATTERNS) {
            const matches = text.matchAll(new RegExp(pattern, 'gi'));
            for (const match of matches) {
                if (match[1]) {
                    dependencies.add(match[1]);
                }
            }
        }
        // Look for task IDs in various formats
        const taskIdPattern = /\b(?:task|issue|ticket|#)\s*(\d+)\b/gi;
        const matches = text.matchAll(taskIdPattern);
        for (const match of matches) {
            if (match[1]) {
                dependencies.add(match[1]);
            }
        }
        return Array.from(dependencies);
    }
    /**
     * Extract technologies and tools mentioned
     */
    async extractTechnologies(text) {
        const technologies = new Set();
        // Common technology patterns
        const techPatterns = [
            /\b(react|angular|vue|svelte)\b/gi,
            /\b(node\.?js|nodejs|express|fastapi|django|flask)\b/gi,
            /\b(typescript|javascript|python|java|c\+\+|rust|go)\b/gi,
            /\b(postgresql|mysql|mongodb|redis|elasticsearch)\b/gi,
            /\b(docker|kubernetes|k8s|aws|azure|gcp)\b/gi,
            /\b(git|github|gitlab|bitbucket)\b/gi,
            /\b(jest|mocha|pytest|junit)\b/gi,
            /\b(webpack|vite|rollup|parcel)\b/gi,
        ];
        for (const pattern of techPatterns) {
            const matches = text.matchAll(pattern);
            for (const match of matches) {
                if (match[1]) {
                    technologies.add(match[1].toLowerCase());
                }
            }
        }
        return Array.from(technologies);
    }
    /**
     * Extract acceptance criteria from text
     */
    async extractAcceptanceCriteria(text) {
        const criteria = [];
        // Look for explicit criteria patterns
        const patterns = [
            /(?:acceptance criteria|requirements?|must|should):\s*([^.!?]+[.!?])/gi,
            /(?:•|\*|-|\d+\.)\s*([^•\*\-\n]+)/g, // Bullet points
        ];
        for (const pattern of patterns) {
            const matches = text.matchAll(pattern);
            for (const match of matches) {
                if (match[1] && match[1].trim().length > 10) {
                    criteria.push(match[1].trim());
                }
            }
        }
        // If no explicit criteria, use AI to extract
        if (criteria.length === 0) {
            try {
                const prompt = `Extract acceptance criteria or requirements from this text. Return as JSON array of criteria strings.

Text: "${text}"

Return only the JSON array, no additional text. Return empty array if no criteria found.`;
                const result = await this.openAIService.complete([{ role: 'user', content: prompt }], { maxTokens: 300, temperature: 0.3 });
                try {
                    const extracted = JSON.parse(result.content.trim());
                    if (Array.isArray(extracted)) {
                        criteria.push(...extracted);
                    }
                }
                catch {
                    // Ignore parse errors
                }
            }
            catch (error) {
                console.error('AI criteria extraction failed:', error);
            }
        }
        return criteria.slice(0, 10); // Limit to 10 criteria
    }
    /**
     * Generate tags from extracted information
     */
    generateTags(keywords, technologies, priority) {
        const tags = new Set();
        // Add priority as tag
        if (priority) {
            tags.add(priority);
        }
        // Add technologies as tags
        technologies.forEach(tech => tags.add(tech));
        // Add top keywords as tags
        keywords.slice(0, 5).forEach(keyword => {
            if (keyword.length > 2 && keyword.length < 20) {
                tags.add(keyword);
            }
        });
        return Array.from(tags);
    }
    /**
     * Calculate confidence score for extracted entities
     */
    calculateConfidence(factors) {
        const weights = {
            hasTitle: 0.3,
            hasDescription: 0.2,
            hasPriority: 0.2,
            hasDeadline: 0.15,
            hasEstimate: 0.15,
        };
        let score = 0;
        for (const [factor, value] of Object.entries(factors)) {
            if (value) {
                score += weights[factor];
            }
        }
        return Math.min(score, 1.0);
    }
};
exports.EntityExtractorService = EntityExtractorService;
exports.EntityExtractorService = EntityExtractorService = __decorate([
    (0, tsyringe_1.injectable)(),
    __metadata("design:paramtypes", [openai_service_1.OpenAIService,
        translation_service_1.TranslationService,
        language_detector_service_1.LanguageDetectorService])
], EntityExtractorService);
//# sourceMappingURL=entity-extractor.service.js.map