Source: api/features/ZptAPI.js

import BaseAPI from '../common/BaseAPI.js';
import { v4 as uuidv4 } from 'uuid';
import NavigationEndpoint from '../../zpt/api/NavigationEndpoint.js';
import CorpuscleSelector from '../../zpt/selection/CorpuscleSelector.js';
import TiltProjector from '../../zpt/selection/TiltProjector.js';
import CorpuscleTransformer from '../../zpt/transform/CorpuscleTransformer.js';
import ParameterValidator from '../../zpt/parameters/ParameterValidator.js';
import ParameterNormalizer from '../../zpt/parameters/ParameterNormalizer.js';
import ZoomLevelMapper from '../../zpt/selection/ZoomLevelMapper.js';
import PanDomainFilter from '../../zpt/selection/PanDomainFilter.js';

/**
 * ZPT API handler for Zero-Point Traversal navigation operations
 * Integrates ZPT services into the main API server following BaseAPI patterns
 */
export default class ZptAPI extends BaseAPI {
    constructor(config = {}) {
        super(config);
        
        // Configuration
        this.maxConcurrentRequests = config.maxConcurrentRequests || 10;
        this.requestTimeoutMs = config.requestTimeoutMs || 120000; // 2 minutes
        this.defaultMaxTokens = config.defaultMaxTokens || 4000;
        this.supportedFormats = config.supportedFormats || ['json', 'markdown', 'structured'];
        this.supportedTokenizers = config.supportedTokenizers || ['cl100k_base', 'p50k_base', 'claude', 'llama'];
        
        // Core dependencies (will be injected)
        this.llmHandler = null;
        this.embeddingHandler = null;
        this.sparqlStore = null;
        this.ragnoCorpus = null; // Will need to be provided or created
        
        // ZPT components
        this.navigationEndpoint = null;
        this.corpuscleSelector = null;
        this.tiltProjector = null;
        this.transformer = null;
        this.validator = new ParameterValidator();
        this.normalizer = new ParameterNormalizer();
        this.zoomMapper = null;
        this.panFilter = null;
        
        // Request tracking
        this.activeRequests = new Map();
        
        // Metrics tracking
        this.metrics = {
            navigations: 0,
            previews: 0,
            transformations: 0,
            errors: 0,
            avgProcessingTime: 0,
            cacheHits: 0,
            cacheMisses: 0
        };
    }

    async initialize() {
        await super.initialize();
        
        // Get dependencies from registry
        const registry = this.config.registry;
        if (!registry) {
            throw new Error('Registry is required for ZptAPI');
        }
        
        try {
            // Get core handlers
            this.llmHandler = registry.get('llm');
            this.embeddingHandler = registry.get('embedding');
            
            // Get storage - try to get SPARQL store if available
            try {
                const memoryManager = registry.get('memory');
                if (memoryManager && memoryManager.storage) {
                    this.sparqlStore = memoryManager.storage;
                }
            } catch (error) {
                this.logger.warn('SPARQL store not available, ZPT functionality will be limited');
            }
            
            // Create mock ragno corpus if SPARQL store available
            // In real implementation, this would query the actual Ragno corpus
            if (this.sparqlStore) {
                this.ragnoCorpus = await this._createCorpusInterface();
            }
            
            // Initialize ZPT components
            await this._initializeZPTComponents();
            
            this.logger.info('ZptAPI initialized successfully');
        } catch (error) {
            this.logger.error('Failed to initialize ZptAPI:', error);
            throw error;
        }
    }

    /**
     * Initialize ZPT-specific components
     */
    async _initializeZPTComponents() {
        // Initialize zoom level mapper
        this.zoomMapper = new ZoomLevelMapper({
            sparqlStore: this.sparqlStore,
            defaultLevel: 'unit'
        });
        
        // Initialize pan domain filter
        this.panFilter = new PanDomainFilter({
            sparqlStore: this.sparqlStore,
            embeddingHandler: this.embeddingHandler
        });
        
        // Initialize corpuscle selector (main selection component)
        if (this.ragnoCorpus) {
            this.corpuscleSelector = new CorpuscleSelector(this.ragnoCorpus, {
                sparqlStore: this.sparqlStore,
                embeddingHandler: this.embeddingHandler,
                maxResults: 1000,
                enableCaching: true
            });
        }
        
        // Initialize tilt projector (representation transformation)
        this.tiltProjector = new TiltProjector({
            embeddingHandler: this.embeddingHandler,
            textAnalyzer: this.llmHandler
        });
        
        // Initialize transformer (output formatting)
        this.transformer = new CorpuscleTransformer({
            enableCaching: true,
            enableMetrics: true,
            defaultFormat: 'structured',
            supportedFormats: this.supportedFormats
        });
        
        // Initialize navigation endpoint (orchestrator)
        if (this.corpuscleSelector && this.tiltProjector && this.transformer) {
            this.navigationEndpoint = new NavigationEndpoint({
                maxConcurrentRequests: this.maxConcurrentRequests,
                requestTimeoutMs: this.requestTimeoutMs,
                enableCaching: true,
                enableMetrics: true
            });
            
            // Initialize with dependencies
            await this.navigationEndpoint.initialize({
                ragnoCorpus: this.ragnoCorpus,
                sparqlStore: this.sparqlStore,
                embeddingHandler: this.embeddingHandler,
                llmHandler: this.llmHandler
            });
        }
    }

    /**
     * Execute a ZPT operation
     */
    async executeOperation(operation, params) {
        this._validateParams(params);
        
        const start = Date.now();
        const requestId = uuidv4();
        
        // Track active request
        this.activeRequests.set(requestId, {
            operation,
            startTime: start,
            params: { ...params, sensitiveData: '[REDACTED]' }
        });
        
        try {
            let result;
            
            switch (operation) {
                case 'navigate':
                    result = await this.navigate(params, requestId);
                    break;
                case 'preview':
                    result = await this.preview(params, requestId);
                    break;
                case 'options':
                    result = await this.getOptions(params, requestId);
                    break;
                case 'schema':
                    result = await this.getSchema(params, requestId);
                    break;
                case 'health':
                    result = await this.getHealth(params, requestId);
                    break;
                default:
                    throw new Error(`Unknown operation: ${operation}`);
            }
            
            const duration = Date.now() - start;
            this.metrics.avgProcessingTime = this._updateAverage(this.metrics.avgProcessingTime, duration);
            this._emitMetric(`zpt.operation.${operation}.duration`, duration);
            this._emitMetric(`zpt.operation.${operation}.count`, 1);
            
            return {
                ...result,
                requestId,
                processingTime: duration
            };
        } catch (error) {
            this.metrics.errors++;
            this._emitMetric(`zpt.operation.${operation}.errors`, 1);
            this.logger.error(`ZPT operation ${operation} failed:`, error);
            throw error;
        } finally {
            this.activeRequests.delete(requestId);
        }
    }

    /**
     * Main navigation operation
     */
    async navigate(params, requestId) {
        if (!this.navigationEndpoint) {
            throw new Error('Navigation endpoint not available without proper corpus/SPARQL setup');
        }
        
        this.logger.info(`Processing navigation request`, { requestId, params: this._sanitizeParams(params) });
        
        try {
            // Validate ZPT parameters
            const validationResult = this.validator.validate(params);
            if (!validationResult.valid) {
                throw new Error(`Parameter validation failed: ${validationResult.message}`);
            }
            
            // Normalize parameters
            const normalizedParams = this.normalizer.normalize(params);
            
            // Execute navigation pipeline via NavigationEndpoint
            const result = await this.navigationEndpoint.handleNavigate({
                body: normalizedParams,
                path: '/api/navigate',
                method: 'POST'
            }, requestId);
            
            this.metrics.navigations++;
            
            return {
                success: true,
                navigation: result.navigation,
                content: result.content,
                metadata: result.metadata,
                diagnostics: result.diagnostics
            };
        } catch (error) {
            // If NavigationEndpoint fails, try fallback implementation
            if (this.corpuscleSelector && this.tiltProjector && this.transformer) {
                return await this._executeNavigationFallback(params, requestId);
            }
            throw error;
        }
    }

    /**
     * Navigation preview (limited processing)
     */
    async preview(params, requestId) {
        this.logger.info(`Processing preview request`, { requestId });
        
        try {
            // Validate parameters
            const validationResult = this.validator.validate(params);
            if (!validationResult.valid) {
                throw new Error(`Parameter validation failed: ${validationResult.message}`);
            }
            
            const normalizedParams = this.normalizer.normalize(params);
            
            if (this.navigationEndpoint) {
                // Use NavigationEndpoint if available
                const result = await this.navigationEndpoint.handlePreview({
                    body: normalizedParams,
                    path: '/api/navigate/preview',
                    method: 'POST'
                }, requestId);
                
                this.metrics.previews++;
                return result;
            } else if (this.corpuscleSelector) {
                // Fallback to direct corpuscle selection
                const selectionResult = await this.corpuscleSelector.select(normalizedParams);
                
                this.metrics.previews++;
                return {
                    success: true,
                    preview: true,
                    navigation: normalizedParams,
                    summary: {
                        corpuscleCount: selectionResult.corpuscles?.length || 0,
                        selectionTime: selectionResult.metadata?.selectionTime || 0,
                        estimatedTokens: this._estimateTokens(selectionResult.corpuscles || []),
                        complexity: selectionResult.metadata?.complexity || 'unknown'
                    },
                    corpuscles: (selectionResult.corpuscles || []).slice(0, 5) // Preview first 5
                };
            } else {
                throw new Error('Preview not available without corpus selector');
            }
        } catch (error) {
            this.logger.error('Preview failed:', error);
            throw new Error(`Preview failed: ${error.message}`);
        }
    }

    /**
     * Get available navigation options
     */
    async getOptions(params = {}, requestId) {
        return {
            success: true,
            options: {
                zoomLevels: ['entity', 'unit', 'text', 'community', 'corpus'],
                tiltRepresentations: ['embedding', 'keywords', 'graph', 'temporal'],
                outputFormats: this.supportedFormats,
                encodingStrategies: ['structured', 'compact', 'inline'],
                maxTokenLimits: {
                    'gpt-4': 128000,
                    'gpt-3.5-turbo': 16385,
                    'claude-3-opus': 200000,
                    'claude-3-sonnet': 200000,
                    'llama': 4096,
                    'mistral': 32000
                },
                supportedTokenizers: this.supportedTokenizers,
                panDomains: {
                    topic: 'Subject area filtering',
                    entity: 'Specific entity constraints',
                    temporal: 'Time-based boundaries',
                    geographic: 'Location-based limits'
                },
                transformOptions: {
                    chunkStrategies: ['semantic', 'fixed', 'adaptive'],
                    metadataInclusion: ['minimal', 'standard', 'comprehensive'],
                    compressionLevels: ['none', 'light', 'aggressive']
                }
            }
        };
    }

    /**
     * Get parameter schema and examples
     */
    async getSchema(params = {}, requestId) {
        return {
            success: true,
            schema: this.validator.getSchema(),
            examples: {
                basic: {
                    zoom: 'unit',
                    tilt: 'keywords',
                    transform: { 
                        maxTokens: this.defaultMaxTokens, 
                        format: 'structured' 
                    }
                },
                advanced: {
                    zoom: 'entity',
                    pan: {
                        topic: 'machine-learning',
                        temporal: {
                            start: '2024-01-01',
                            end: '2024-12-31'
                        }
                    },
                    tilt: 'embedding',
                    transform: {
                        maxTokens: 8000,
                        format: 'json',
                        includeMetadata: true,
                        chunkStrategy: 'semantic',
                        tokenizer: 'cl100k_base'
                    }
                },
                minimal: {
                    zoom: 'text'
                }
            },
            documentation: {
                zoom: 'Abstraction level: entity (specific elements), unit (semantic chunks), text (full content), community (summaries), corpus (overview)',
                pan: 'Domain filtering: topic (subject constraints), entity (specific scope), temporal (time bounds), geographic (location limits)',
                tilt: 'Representation format: embedding (vectors), keywords (terms), graph (relationships), temporal (sequences)',
                transform: 'Output options: maxTokens (budget), format (structure), tokenizer (model), metadata (context)'
            },
            defaults: this.validator.getDefaults()
        };
    }

    /**
     * Get ZPT system health
     */
    async getHealth(params = {}, requestId) {
        const health = {
            status: 'healthy',
            components: {
                validator: { status: !!this.validator ? 'healthy' : 'unavailable' },
                normalizer: { status: !!this.normalizer ? 'healthy' : 'unavailable' },
                corpuscleSelector: { status: !!this.corpuscleSelector ? 'healthy' : 'unavailable' },
                tiltProjector: { status: !!this.tiltProjector ? 'healthy' : 'unavailable' },
                transformer: { status: !!this.transformer ? 'healthy' : 'unavailable' },
                navigationEndpoint: { status: !!this.navigationEndpoint ? 'healthy' : 'unavailable' },
                sparqlStore: { status: !!this.sparqlStore ? 'healthy' : 'unavailable' },
                embeddingHandler: { status: !!this.embeddingHandler ? 'healthy' : 'unavailable' },
                llmHandler: { status: !!this.llmHandler ? 'healthy' : 'unavailable' }
            },
            capabilities: {
                fullNavigation: !!this.navigationEndpoint,
                corpuscleSelection: !!this.corpuscleSelector,
                tiltProjection: !!this.tiltProjector,
                transformation: !!this.transformer,
                sparqlIntegration: !!this.sparqlStore,
                embeddingSearch: !!this.embeddingHandler,
                llmAnalysis: !!this.llmHandler
            },
            activeRequests: this.activeRequests.size,
            metrics: this.metrics
        };
        
        // Determine overall health
        const criticalComponents = ['validator', 'normalizer'];
        const hasCriticalIssues = criticalComponents.some(comp => 
            health.components[comp].status !== 'healthy'
        );
        
        const hasCapabilities = health.capabilities.corpuscleSelection || 
                               health.capabilities.fullNavigation;
        
        if (hasCriticalIssues) {
            health.status = 'unhealthy';
        } else if (!hasCapabilities) {
            health.status = 'degraded';
        }
        
        return {
            success: true,
            health
        };
    }

    /**
     * Fallback navigation implementation when NavigationEndpoint is not available
     */
    async _executeNavigationFallback(params, requestId) {
        this.logger.info('Using fallback navigation implementation', { requestId });
        
        const normalizedParams = this.normalizer.normalize(params);
        
        // Stage 1: Corpuscle Selection
        const selectionStart = Date.now();
        const selectionResult = await this.corpuscleSelector.select(normalizedParams);
        const selectionTime = Date.now() - selectionStart;
        
        // Stage 2: Tilt Projection
        const projectionStart = Date.now();
        const projectionResult = await this.tiltProjector.project(
            selectionResult.corpuscles || [],
            normalizedParams.tilt || 'keywords',
            {
                embeddingHandler: this.embeddingHandler,
                textAnalyzer: this.llmHandler
            }
        );
        const projectionTime = Date.now() - projectionStart;
        
        // Stage 3: Transformation
        const transformStart = Date.now();
        const transformResult = await this.transformer.transform(
            projectionResult,
            selectionResult,
            normalizedParams.transform || {}
        );
        const transformTime = Date.now() - transformStart;
        
        this.metrics.navigations++;
        
        return {
            success: true,
            navigation: normalizedParams,
            content: transformResult.content,
            metadata: {
                pipeline: {
                    selectionTime,
                    projectionTime,
                    transformTime,
                    totalTime: selectionTime + projectionTime + transformTime
                },
                selection: selectionResult.metadata,
                projection: projectionResult.metadata,
                transformation: transformResult.metadata,
                fallbackUsed: true
            },
            diagnostics: {
                requestId,
                stages: ['selection', 'projection', 'transformation'],
                performance: { selectionTime, projectionTime, transformTime },
                implementation: 'fallback'
            }
        };
    }

    /**
     * Create a basic corpus interface for ZPT components
     */
    async _createCorpusInterface() {
        // This is a simplified interface - in real implementation would query Ragno corpus
        return {
            // Mock corpus interface
            query: async (criteria) => {
                this.logger.debug('Querying corpus with criteria:', criteria);
                return []; // Empty results for now
            },
            getMetadata: async () => ({
                totalUnits: 0,
                totalEntities: 0,
                lastUpdated: new Date().toISOString()
            })
        };
    }

    /**
     * Estimate tokens for corpuscles (simple heuristic)
     */
    _estimateTokens(corpuscles) {
        return corpuscles.reduce((total, corpuscle) => {
            const content = Object.values(corpuscle.content || {}).join(' ');
            return total + Math.ceil(content.length / 4); // Rough 4 chars per token
        }, 0);
    }

    /**
     * Update running average
     */
    _updateAverage(currentAvg, newValue) {
        const count = this.metrics.navigations + this.metrics.previews + 1;
        return (currentAvg * (count - 1) + newValue) / count;
    }

    /**
     * Sanitize parameters for logging (remove sensitive data)
     */
    _sanitizeParams(params) {
        const sanitized = { ...params };
        // Remove or mask any sensitive fields
        if (sanitized.apiKey) sanitized.apiKey = '[REDACTED]';
        if (sanitized.token) sanitized.token = '[REDACTED]';
        return sanitized;
    }

    /**
     * Store interaction (inherited from BaseAPI)
     */
    async storeInteraction(interaction) {
        // ZPT doesn't directly store interactions, but processes navigation requests
        throw new Error('Use navigate operation to process ZPT navigation requests');
    }

    /**
     * Retrieve interactions (inherited from BaseAPI)
     */
    async retrieveInteractions(query) {
        // Return navigation history if implemented
        throw new Error('Navigation history retrieval not yet implemented');
    }

    /**
     * Get ZPT API metrics
     */
    async getMetrics() {
        const baseMetrics = await super.getMetrics();
        
        return {
            ...baseMetrics,
            zpt: this.metrics,
            components: {
                navigationEndpoint: !!this.navigationEndpoint,
                corpuscleSelector: !!this.corpuscleSelector,
                tiltProjector: !!this.tiltProjector,
                transformer: !!this.transformer,
                sparqlStore: !!this.sparqlStore,
                ragnoCorpus: !!this.ragnoCorpus
            },
            activeRequests: Array.from(this.activeRequests.values())
        };
    }

    async shutdown() {
        this.logger.info('Shutting down ZptAPI');
        
        // Wait for active requests to complete (with timeout)
        const shutdownTimeout = 30000; // 30 seconds
        const startTime = Date.now();
        
        while (this.activeRequests.size > 0 && Date.now() - startTime < shutdownTimeout) {
            await new Promise(resolve => setTimeout(resolve, 1000));
        }
        
        if (this.activeRequests.size > 0) {
            this.logger.warn(`Forced shutdown with ${this.activeRequests.size} active requests`);
        }
        
        // Shutdown navigation endpoint if available
        if (this.navigationEndpoint && typeof this.navigationEndpoint.shutdown === 'function') {
            await this.navigationEndpoint.shutdown();
        }
        
        // Cleanup other components
        if (this.transformer && typeof this.transformer.dispose === 'function') {
            await this.transformer.dispose();
        }
        
        if (this.corpuscleSelector && typeof this.corpuscleSelector.dispose === 'function') {
            await this.corpuscleSelector.dispose();
        }
        
        this.activeRequests.clear();
        
        await super.shutdown();
    }
}