"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.conversationEngine = exports.ConversationEngine = void 0;
const logger_js_1 = require("../../utils/logger.js");
const index_js_1 = require("../data-store/index.js");
const client_js_1 = require("../openrouter/client.js");
const index_js_2 = require("../queue-system/index.js");
const history_js_1 = require("./history.js");
const system_prompt_js_1 = require("./system-prompt.js");
const sales_redirect_js_1 = require("./sales-redirect.js");
const index_js_3 = require("../timer-system/index.js");
const index_js_4 = require("../prompt-library/index.js");
const ai_prompt_generator_js_1 = require("../image-engine/ai-prompt-generator.js");
const log = (0, logger_js_1.createModuleLogger)('conversation-engine');
const DEFAULT_BUFFER_MS = 3000;
/** Minimum messages between photo sends per user */
const PHOTO_COOLDOWN_MESSAGES = 5;
class ConversationEngine {
    /** characterId:userId → buffered messages */
    buffers = new Map();
    /** characterId:userId → custom buffer duration */
    bufferDurations = new Map();
    static bufferKey(characterId, userId) {
        return `${characterId}:${userId}`;
    }
    /**
     * Main entry point for incoming messages.
     * Buffers consecutive messages and merges them before generating a response.
     */
    async handleIncomingMessage(characterId, userId, text, userName) {
        const key = ConversationEngine.bufferKey(characterId, userId);
        const bufferMs = this.bufferDurations.get(key) ?? DEFAULT_BUFFER_MS;
        const existing = this.buffers.get(key);
        if (existing) {
            // Append to existing buffer and reset timer
            existing.texts.push(text);
            if (userName)
                existing.userName = userName;
            clearTimeout(existing.timer);
            existing.timer = setTimeout(() => this.flushBuffer(characterId, userId), bufferMs);
            log.info({ characterId, userId, buffered: existing.texts.length }, 'Message buffered');
            return;
        }
        // Create new buffer entry
        const entry = {
            texts: [text],
            userName,
            timer: setTimeout(() => this.flushBuffer(characterId, userId), bufferMs),
        };
        this.buffers.set(key, entry);
        log.info({ characterId, userId }, 'New message buffer started');
    }
    /**
     * Sets the buffer time for message merging for a specific character-user pair.
     */
    setMessageBuffer(characterId, userId, bufferMs) {
        const key = ConversationEngine.bufferKey(characterId, userId);
        this.bufferDurations.set(key, Math.max(0, bufferMs));
    }
    /**
     * Generates an AI response using OpenRouter.
     * Builds system prompt from character DNA + profile + history,
     * then sends to OpenRouter for completion.
     */
    async generateResponse(characterId, userId) {
        const [dna, profile, history, persona, entities] = await Promise.all([
            index_js_1.dataStore.getCharacter(characterId),
            index_js_1.dataStore.getConversationProfile(characterId),
            history_js_1.conversationManager.getHistory(characterId, userId),
            index_js_1.dataStore.getConversationPersona(characterId),
            index_js_1.dataStore.getTelegramEntities(characterId),
        ]);
        if (!dna)
            throw new Error(`Character not found: ${characterId}`);
        if (!profile)
            throw new Error(`Conversation profile not found: ${characterId}`);
        // Build channel links from telegram entities
        let channelLinks;
        if (entities && entities.length > 0) {
            const normalChannel = entities.find(e => e.type === 'channel' && !e.isVip && e.inviteLink);
            const vipChannel = entities.find(e => e.type === 'channel' && e.isVip && e.inviteLink);
            if (normalChannel || vipChannel) {
                channelLinks = {};
                if (normalChannel?.inviteLink)
                    channelLinks.normal = normalChannel.inviteLink;
                if (vipChannel?.inviteLink) {
                    channelLinks.vip = vipChannel.inviteLink;
                    if (vipChannel.starPrice)
                        channelLinks.vipPrice = vipChannel.starPrice;
                }
            }
        }
        const systemPrompt = (0, system_prompt_js_1.buildSystemPrompt)(dna, profile, history ?? undefined, persona ?? undefined, channelLinks);
        // Build message array from recent history
        const recentMessages = await history_js_1.conversationManager.getRecentMessages(characterId, userId, 72);
        const messages = [
            { role: 'system', content: systemPrompt },
        ];
        for (const msg of recentMessages) {
            messages.push({ role: msg.role, content: msg.content });
        }
        // Check if we should add a sales pitch hint
        if (history && sales_redirect_js_1.salesRedirector.shouldPitch(history, profile)) {
            const pitchHint = sales_redirect_js_1.salesRedirector.generatePitchHint(profile);
            messages.push({
                role: 'system',
                content: `[Satış yönlendirme ipucu: ${pitchHint}]`,
            });
        }
        // Check photo cooldown — inject warning if recently sent
        if (history && this.isPhotoCooldownActive(history)) {
            messages.push({
                role: 'system',
                content: `[UYARI: Son ${PHOTO_COOLDOWN_MESSAGES} mesaj içinde zaten fotoğraf gönderildi. [FOTO] etiketi KULLANMA. Kullanıcı isterse "biraz sonra atarım", "bekle bi" gibi doğal bahaneler kullan.]`,
            });
        }
        const client = (0, client_js_1.createOpenRouterClient)();
        const response = await client.chatCompletion(messages, {
            temperature: 0.85,
            max_tokens: 120,
        });
        let content = response.choices?.[0]?.message?.content ?? '';
        // Strip HTML tags that AI sometimes injects (e.g. <div>, </div>, <br>, <p>)
        content = content.replace(/<\/?[^>]+(>|$)/g, '').trim();
        // Strip markdown formatting (bold, italic, headers)
        content = content.replace(/\*\*(.+?)\*\*/g, '$1');
        content = content.replace(/\*(.+?)\*/g, '$1');
        content = content.replace(/__(.+?)__/g, '$1');
        content = content.replace(/_(.+?)_/g, '$1');
        content = content.replace(/^#+\s*/gm, '');
        // Strip [FOTO] if cooldown is active (safety net)
        if (history && this.isPhotoCooldownActive(history)) {
            content = content.replace(/\[FOTO\]/gi, '').trim();
        }
        log.info({ characterId, userId, responseLength: content.length }, 'Response generated');
        return content;
    }
    /**
     * Checks if a photo was sent within the last N messages.
     */
    isPhotoCooldownActive(history) {
        const recent = history.messages.slice(-PHOTO_COOLDOWN_MESSAGES);
        return recent.some(m => m.role === 'assistant' && m.metadata?.photoSent);
    }
    /**
     * Parses [FOTO] tag from response text.
     * Returns { text, hasPhoto } where text has the tag removed.
     */
    parsePhotoTag(text) {
        const hasPhoto = /\[FOTO\]/i.test(text);
        const cleanText = text.replace(/\[FOTO\]/gi, '').trim();
        return { text: cleanText, hasPhoto };
    }
    /**
     * Flushes the message buffer: merges texts, saves to history,
     * generates a response with delay, parses [FOTO] tags, and queues for sending.
     */
    async flushBuffer(characterId, userId) {
        const key = ConversationEngine.bufferKey(characterId, userId);
        const entry = this.buffers.get(key);
        if (!entry)
            return;
        this.buffers.delete(key);
        const mergedText = entry.texts.join('\n');
        try {
            // Update user info if userName is available
            if (entry.userName) {
                await history_js_1.conversationManager.updateUserInfo(characterId, userId, { name: entry.userName });
            }
            // Save user message to history
            await history_js_1.conversationManager.addMessage(characterId, userId, 'user', mergedText);
            // Generate AI response
            const responseText = await this.generateResponse(characterId, userId);
            // Parse [FOTO] tag from response
            const { text: cleanText, hasPhoto } = this.parsePhotoTag(responseText);
            // Save assistant response to history (with photo metadata)
            const history = await history_js_1.conversationManager.addMessage(characterId, userId, 'assistant', cleanText, hasPhoto ? { photoSent: true } : undefined);
            // Record pitch if sales redirector suggested one
            const profile = await index_js_1.dataStore.getConversationProfile(characterId);
            if (profile && history) {
                const preHistory = await history_js_1.conversationManager.getHistory(characterId, userId);
                if (preHistory && sales_redirect_js_1.salesRedirector.shouldPitch(preHistory, profile)) {
                    const updated = sales_redirect_js_1.salesRedirector.recordPitch(preHistory);
                    await index_js_1.dataStore.saveConversationHistory(updated);
                }
            }
            // Calculate realistic delay before sending
            const hour = new Date().getHours();
            const delaySec = (0, index_js_3.calculateDelay)(index_js_3.DEFAULT_TIMER_CONFIG, cleanText.length, hour);
            const delayMs = Math.round(delaySec * 1000);
            log.info({ characterId, userId, delaySec: delaySec.toFixed(1), hasPhoto }, 'Scheduling response with delay');
            // Schedule response after delay
            setTimeout(async () => {
                try {
                    // Queue the text response for sending via Telegram
                    if (cleanText) {
                        await index_js_2.queue.add('chat_response', characterId, {
                            userId,
                            text: cleanText,
                            characterId,
                        });
                    }
                    // Queue photo generation if [FOTO] was present
                    if (hasPhoto) {
                        await this.queuePhotoForUser(characterId, userId);
                    }
                    log.info({ characterId, userId, mergedCount: entry.texts.length }, 'Buffer flushed and response queued');
                }
                catch (err) {
                    log.error({ characterId, userId, error: err.message }, 'Failed to queue delayed response');
                }
            }, delayMs);
        }
        catch (err) {
            log.error({ characterId, userId, error: err.message }, 'Failed to process buffered messages');
        }
    }
    /**
     * Queues a photo generation task for a user conversation.
     * Uses AI prompt generator trained on 900+ prompt library to create
     * character-personalized, context-aware image prompts.
     */
    async queuePhotoForUser(characterId, userId) {
        try {
            const dna = await index_js_1.dataStore.getCharacter(characterId);
            if (!dna)
                return;
            // Get recent user messages for context
            const recentMessages = await history_js_1.conversationManager.getRecentMessages(characterId, userId, 24);
            const userTexts = recentMessages
                .filter(m => m.role === 'user')
                .slice(-5)
                .map(m => m.content);
            try {
                // AI-powered prompt generation from 900+ prompt library
                const generated = await (0, ai_prompt_generator_js_1.generateImagePrompt)(dna, {
                    context: userTexts,
                });
                await index_js_2.queue.add('image_generation', characterId, {
                    prompt: generated.prompt,
                    model: 'nano-banana-2',
                    aspectRatio: generated.aspectRatio,
                    targetUserId: userId,
                });
                log.info({ characterId, userId, category: generated.category, safetyModified: generated.safetyModified }, 'AI-generated photo prompt queued');
            }
            catch (aiErr) {
                log.warn({ characterId, userId, error: aiErr.message }, 'AI prompt generation failed, falling back to library');
                // Fallback: prompt kütüphanesinden seç
                const prompt = await (0, index_js_4.getContextAwarePrompt)(dna, userTexts);
                if (prompt) {
                    await index_js_2.queue.add('image_generation', characterId, {
                        prompt: prompt.flatPrompt,
                        model: 'nano-banana-2',
                        aspectRatio: prompt.aspectRatio,
                        targetUserId: userId,
                    });
                    log.info({ characterId, userId, promptId: prompt.originalId }, 'Fallback library prompt queued');
                }
                else {
                    // Son çare: basit selfie prompt
                    const subject = `${dna.identity.name}, ${dna.identity.age} year old ${dna.identity.nationality} woman`;
                    await index_js_2.queue.add('image_generation', characterId, {
                        prompt: `${subject}, casual selfie, smiling at camera, natural lighting, instagram style photo`,
                        model: 'nano-banana-2',
                        aspectRatio: '9:16',
                        targetUserId: userId,
                    });
                }
            }
        }
        catch (err) {
            log.error({ characterId, userId, error: err.message }, 'Failed to queue photo for user');
        }
    }
    /** Expose buffer state for testing */
    _getBuffer(characterId, userId) {
        return this.buffers.get(ConversationEngine.bufferKey(characterId, userId));
    }
    /** Clear all buffers (useful for cleanup) */
    _clearBuffers() {
        for (const entry of this.buffers.values()) {
            clearTimeout(entry.timer);
        }
        this.buffers.clear();
    }
}
exports.ConversationEngine = ConversationEngine;
/** Singleton instance */
exports.conversationEngine = new ConversationEngine();
//# sourceMappingURL=index.js.map