/**
 * Conversation Selector for Loop Mode
 * Handles discovery and selection of stored conversations from Redis
 */
import inquirer from "inquirer";
import chalk from "chalk";
import { createRedisClient, scanKeys, deserializeConversation, getNormalizedConfig, } from "../../lib/utils/redis.js";
import { logger } from "../../lib/utils/logger.js";
import { LOOP_CACHE_CONFIG, LOOP_DISPLAY_LIMITS, generateConversationTitle, truncateText, formatTimeAgo, getContentIcon, } from "../../lib/utils/loopUtils.js";
export class ConversationSelector {
    redisClient = null;
    redisConfig;
    conversationCache = null;
    cacheTimestamp = 0;
    constructor(redisConfig = {}) {
        this.redisConfig = getNormalizedConfig(redisConfig);
    }
    /**
     * Initialize Redis connection
     */
    async initializeRedis() {
        if (!this.redisClient) {
            // Cast is necessary: createRedisClient returns the ioredis client type which
            // does not structurally match our RedisClient interface due to overloaded
            // method signatures. The runtime value is fully compatible.
            this.redisClient = (await createRedisClient(this.redisConfig));
        }
    }
    /**
     * Get available conversations for a user
     */
    async getAvailableConversations(userId) {
        // Check if cached conversations are still valid (within TTL)
        if (this.conversationCache &&
            Date.now() - this.cacheTimestamp < LOOP_CACHE_CONFIG.TTL_MS) {
            logger.debug("Using cached conversation list");
            return this.filterConversationsByUser(this.conversationCache, userId);
        }
        try {
            await this.initializeRedis();
            if (!this.redisClient) {
                throw new Error("Redis client not available");
            }
            const keys = await this.scanConversationKeys();
            if (keys.length === 0) {
                logger.debug("No conversations found in Redis");
                return [];
            }
            const summaries = await this.processConversationKeys(keys);
            const sortedSummaries = this.sortConversationsByDate(summaries);
            this.updateCache(sortedSummaries);
            return this.filterConversationsByUser(sortedSummaries, userId);
        }
        catch (error) {
            return this.handleRetrievalError(error);
        }
    }
    /**
     * Display conversation menu and get user selection
     */
    async displayConversationMenu(userId) {
        try {
            const conversations = await this.getAvailableConversations(userId);
            if (conversations.length === 0) {
                logger.debug("No conversations available for selection");
                return "NEW_CONVERSATION";
            }
            const choices = this.createMenuChoices(conversations);
            return await this.showSelectionPrompt(choices);
        }
        catch (error) {
            return this.handleMenuError(error);
        }
    }
    /**
     * Check if there are any stored conversations
     */
    async hasStoredConversations(userId) {
        try {
            const conversations = await this.getAvailableConversations(userId);
            return conversations.length > 0;
        }
        catch (error) {
            logger.debug("Failed to check for stored conversations:", error);
            return false;
        }
    }
    /**
     * Close Redis connection
     */
    async close() {
        if (this.redisClient) {
            await this.redisClient.quit();
            this.redisClient = null;
        }
    }
    async scanConversationKeys() {
        if (!this.redisClient) {
            throw new Error("Redis client not initialized");
        }
        const pattern = `${this.redisConfig.keyPrefix}*`;
        const keys = await scanKeys(this.redisClient, pattern);
        logger.debug(`Found ${keys.length} conversation keys in Redis`);
        return keys;
    }
    async processConversationKeys(keys) {
        const summaries = [];
        for (const key of keys) {
            const summary = await this.processSingleConversationKey(key);
            if (summary) {
                summaries.push(summary);
            }
        }
        return summaries;
    }
    async processSingleConversationKey(key) {
        if (!this.redisClient) {
            logger.warn(`Redis client not available for key ${key}`);
            return null;
        }
        try {
            const conversationData = await this.redisClient.get(key);
            const conversation = deserializeConversation(conversationData);
            if (conversation &&
                conversation.messages &&
                conversation.messages.length > 0) {
                // Only include conversations with session IDs prefixed with "NL_"
                if (!conversation.sessionId?.startsWith("NL_")) {
                    return null;
                }
                return this.createConversationSummary(conversation);
            }
            return null;
        }
        catch (error) {
            logger.warn(`Failed to process conversation key ${key}:`, error);
            return null;
        }
    }
    sortConversationsByDate(summaries) {
        return summaries.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
    }
    updateCache(summaries) {
        this.conversationCache = summaries;
        this.cacheTimestamp = Date.now();
        logger.debug(`Retrieved ${summaries.length} valid conversations`);
    }
    filterConversationsByUser(summaries, userId) {
        if (!userId) {
            return summaries;
        }
        return summaries.filter((summary) => summary.userId === userId);
    }
    /*
     * Create menu choices for inquirer prompt
     */
    createMenuChoices(conversations) {
        const choices = [
            {
                name: chalk.green("🆕 Start New Conversation"),
                value: "NEW_CONVERSATION",
                short: "New Conversation",
            },
            // Cast is intentional: inquirer's Separator type is not exported cleanly
            // and does not overlap with MenuChoice, but inquirer accepts it at runtime.
            new inquirer.Separator(),
        ];
        for (const conversation of conversations.slice(0, LOOP_DISPLAY_LIMITS.MAX_CONVERSATIONS)) {
            const choice = this.formatConversationChoice(conversation);
            choices.push(choice);
        }
        return choices;
    }
    async showSelectionPrompt(choices) {
        const answer = await inquirer.prompt([
            {
                type: "select",
                name: "selectedConversation",
                message: "Select a conversation to continue:",
                choices,
                pageSize: LOOP_DISPLAY_LIMITS.PAGE_SIZE,
            },
        ]);
        return answer.selectedConversation;
    }
    handleRetrievalError(error) {
        logger.error("Failed to retrieve conversations:", error);
        return [];
    }
    handleMenuError(error) {
        logger.error("Failed to display conversation menu:", error);
        return "NEW_CONVERSATION";
    }
    createConversationSummary(conversation) {
        const messages = conversation.messages;
        const firstMessage = messages[0];
        const lastMessage = messages[messages.length - 1];
        return {
            sessionId: conversation.sessionId,
            id: conversation.id,
            title: conversation.title || generateConversationTitle(firstMessage.content),
            firstMessage: {
                content: truncateText(firstMessage.content, LOOP_DISPLAY_LIMITS.CONTENT_LENGTH),
                timestamp: firstMessage.timestamp || conversation.createdAt,
            },
            lastMessage: {
                content: truncateText(lastMessage.content, LOOP_DISPLAY_LIMITS.CONTENT_LENGTH),
                timestamp: lastMessage.timestamp || conversation.updatedAt,
            },
            messageCount: messages.length,
            userId: conversation.userId,
            duration: formatTimeAgo(conversation.updatedAt),
            createdAt: conversation.createdAt,
            updatedAt: conversation.updatedAt,
        };
    }
    formatConversationChoice(summary) {
        const icon = getContentIcon(summary.firstMessage.content);
        const title = chalk.white(summary.title || "Untitled Conversation");
        const duration = chalk.gray(`(${summary.duration})`);
        const details = chalk.gray(`     └ ${summary.messageCount} message${summary.messageCount !== 1 ? "s" : ""} | ` +
            `Session: ${summary.sessionId.slice(0, LOOP_DISPLAY_LIMITS.SESSION_ID_DISPLAY)}... | ` +
            `Updated: ${new Date(summary.updatedAt).toLocaleString()}`);
        const name = `${icon} ${title} ${duration}\n${details}`;
        return {
            name,
            value: summary.sessionId,
            short: `${summary.title} (${summary.sessionId.slice(0, LOOP_DISPLAY_LIMITS.SESSION_ID_SHORT)}...)`,
        };
    }
}
//# sourceMappingURL=conversationSelector.js.map