Source: api/features/SearchAPI.js

import BaseAPI from '../common/BaseAPI.js';
import { v4 as uuidv4 } from 'uuid';

/**
 * Search API handler for semantic search capabilities
 */
export default class SearchAPI extends BaseAPI {
    constructor(config = {}) {
        super(config);
        this.memoryManager = null;
        this.searchService = null;
        this.similarityThreshold = config.similarityThreshold || 0.7;
        this.defaultLimit = config.defaultLimit || 5;
    }

    async initialize() {
        await super.initialize();
        
        // Get dependencies from registry
        const registry = this.config.registry;
        if (!registry) {
            throw new Error('Registry is required for SearchAPI');
        }
        
        try {
            this.memoryManager = registry.get('memory');
            
            // SearchService is optional, try to get it if available
            try {
                this.searchService = registry.get('search');
            } catch (error) {
                this.logger.warn('Search service not available, using memory manager for search');
            }
            
            this.logger.info('SearchAPI initialized successfully');
        } catch (error) {
            this.logger.error('Failed to initialize SearchAPI:', error);
            throw error;
        }
    }

    /**
     * Execute a search operation
     */
    async executeOperation(operation, params) {
        this._validateParams(params);
        
        const start = Date.now();
        
        try {
            let result;
            
            switch (operation) {
                case 'search':
                    result = await this.searchContent(params);
                    break;
                case 'index':
                    result = await this.indexContent(params);
                    break;
                default:
                    throw new Error(`Unknown operation: ${operation}`);
            }
            
            const duration = Date.now() - start;
            this._emitMetric(`search.${operation}.duration`, duration);
            this._emitMetric(`search.${operation}.count`, 1);
            
            return result;
        } catch (error) {
            this._emitMetric(`search.${operation}.errors`, 1);
            throw error;
        }
    }

    /**
     * Search content using semantic similarity
     */
    async searchContent({ query, limit = this.defaultLimit, types, threshold }) {
        if (!query) {
            throw new Error('Query is required');
        }

        try {
            const similarityThreshold = threshold || this.similarityThreshold;
            
            // Use dedicated search service if available, otherwise fall back to memory manager
            let results = [];
            
            try {
                if (this.searchService) {
                    this.logger.info('Using search service for query:', query);
                    results = await this.searchService.search(query, {
                        threshold: similarityThreshold,
                        limit,
                        types: types ? types.split(',') : undefined
                    });
                } else {
                    // Fall back to memory manager for search
                    this.logger.info('Using memory manager fallback for search');
                    
                    try {
                        // Generate embedding safely
                        const embedding = await this.memoryManager.generateEmbedding(query);
                        this.logger.info('Embedding generated successfully');
                        
                        // Retrieve interactions safely
                        let memories = [];
                        try {
                            memories = await this.memoryManager.retrieveRelevantInteractions(
                                query,
                                similarityThreshold,
                                0,
                                limit
                            );
                            this.logger.info(`Retrieved ${memories?.length || 0} memories`);
                        } catch (retrievalError) {
                            this.logger.error('Error retrieving interactions:', retrievalError);
                            memories = []; // Fallback to empty array
                        }
                        
                        // Ensure memories is an array before processing
                        if (!Array.isArray(memories)) {
                            this.logger.warn('Memory retrieval did not return an array');
                            memories = [];
                        }
                        
                        // Apply safe transformation with defensive coding
                        results = memories
                            .filter(item => item && typeof item === 'object' && item.interaction)
                            .map(item => {
                                try {
                                    const interaction = item.interaction || {};
                                    return {
                                        id: interaction.id || uuidv4(),
                                        title: (interaction.metadata && interaction.metadata.title) || 
                                              (interaction.prompt && interaction.prompt.slice(0, 50)) || 
                                              'Untitled',
                                        content: `${interaction.prompt || ''}\n${interaction.output || ''}`,
                                        similarity: typeof item.similarity === 'number' ? item.similarity : 0,
                                        type: (interaction.metadata && interaction.metadata.type) || 'memory',
                                        metadata: interaction.metadata || {}
                                    };
                                } catch (transformError) {
                                    this.logger.error('Error transforming memory item:', transformError);
                                    // Return a default item on transform error
                                    return {
                                        id: uuidv4(),
                                        title: 'Error Item',
                                        content: 'Could not process this item',
                                        similarity: 0,
                                        type: 'error',
                                        metadata: {}
                                    };
                                }
                            });
                    } catch (embeddingError) {
                        this.logger.error('Error generating embedding:', embeddingError);
                        // Return empty results on embedding error
                        results = [];
                    }
                }
            } catch (searchError) {
                this.logger.error('Error during search process:', searchError);
                // Return empty results on search error
                results = [];
            }
            
            this._emitMetric('search.count', 1);
            return { 
                results,
                count: results.length
            };
        } catch (error) {
            this.logger.error('Search operation error:', error);
            this._emitMetric('search.errors', 1);
            
            // Return empty results rather than throwing, for API stability
            return { 
                results: [],
                count: 0,
                error: error.message
            };
        }
    }

    /**
     * Index content for search
     */
    async indexContent({ content, type, title, metadata = {} }) {
        if (!content) {
            throw new Error('Content is required');
        }
        
        if (!type) {
            throw new Error('Content type is required');
        }

        try {
            let result;
            
            // Use dedicated search service if available
            if (this.searchService) {
                const id = uuidv4();
                await this.searchService.index({
                    id,
                    content,
                    type,
                    title: title || content.slice(0, 50),
                    metadata
                });
                
                result = { id, success: true };
            } else {
                // Fall back to storing in memory
                const embedding = await this.memoryManager.generateEmbedding(content);
                const concepts = await this.memoryManager.extractConcepts(content);
                
                const id = uuidv4();
                await this.memoryManager.addInteraction(
                    title || content.slice(0, 50),
                    content,
                    embedding,
                    concepts,
                    {
                        id,
                        type,
                        timestamp: Date.now(),
                        ...metadata
                    }
                );
                
                result = { id, success: true };
            }
            
            this._emitMetric('index.count', 1);
            return result;
        } catch (error) {
            this._emitMetric('index.errors', 1);
            throw error;
        }
    }

    /**
     * Get search API metrics
     */
    async getMetrics() {
        const baseMetrics = await super.getMetrics();
        
        const serviceInfo = this.searchService 
            ? { service: 'dedicated', indexSize: await this._getIndexSize() }
            : { service: 'memory-fallback' };
        
        return {
            ...baseMetrics,
            search: {
                ...serviceInfo,
                operations: {
                    search: {
                        count: await this._getMetricValue('search.count'),
                        errors: await this._getMetricValue('search.errors'),
                        duration: await this._getMetricValue('search.duration')
                    },
                    index: {
                        count: await this._getMetricValue('index.count'),
                        errors: await this._getMetricValue('index.errors')
                    }
                }
            }
        };
    }

    async _getIndexSize() {
        if (this.searchService && typeof this.searchService.getIndexSize === 'function') {
            return await this.searchService.getIndexSize();
        }
        return 0;
    }

    async _getMetricValue(metricName) {
        // In a real implementation, this would fetch from a metrics store
        return 0;
    }
}