Source: zpt/api/NavigationEndpoint.js

/**
 * RESTful navigation handler for ZPT corpus navigation API
 */
import ParameterValidator from '../parameters/ParameterValidator.js';
import ParameterNormalizer from '../parameters/ParameterNormalizer.js';
import CorpuscleSelector from '../selection/CorpuscleSelector.js';
import TiltProjector from '../selection/TiltProjector.js';
import CorpuscleTransformer from '../transform/CorpuscleTransformer.js';
import RequestParser from './RequestParser.js';
import ResponseFormatter from './ResponseFormatter.js';
import ErrorHandler from './ErrorHandler.js';
import { logger } from '../../Utils.js';

export default class NavigationEndpoint {
    constructor(options = {}) {
        this.config = {
            maxConcurrentRequests: options.maxConcurrentRequests || 10,
            requestTimeoutMs: options.requestTimeoutMs || 120000, // 2 minutes
            enableRateLimit: options.enableRateLimit !== false,
            rateLimit: options.rateLimit || { requests: 100, windowMs: 60000 }, // 100 req/min
            enableCaching: options.enableCaching !== false,
            enableMetrics: options.enableMetrics !== false,
            corsEnabled: options.corsEnabled !== false,
            ...options
        };

        // Initialize components
        this.validator = new ParameterValidator();
        this.normalizer = new ParameterNormalizer();
        this.requestParser = new RequestParser();
        this.responseFormatter = new ResponseFormatter();
        this.errorHandler = new ErrorHandler();

        // Navigation components (injected via dependencies)
        this.corpuscleSelector = null;
        this.tiltProjector = null;
        this.transformer = null;

        // Request tracking and metrics
        this.activeRequests = new Map();
        this.requestMetrics = {
            totalRequests: 0,
            successfulRequests: 0,
            failedRequests: 0,
            avgResponseTime: 0,
            requestsByEndpoint: new Map()
        };

        // Rate limiting
        this.rateLimitStore = new Map();

        this.initializeEndpoints();
    }

    /**
     * Initialize endpoint configurations
     */
    initializeEndpoints() {
        this.endpoints = {
            '/api/navigate': {
                methods: ['POST'],
                handler: this.handleNavigate.bind(this),
                description: 'Main navigation endpoint',
                rateLimit: true,
                timeout: this.config.requestTimeoutMs
            },
            '/api/navigate/preview': {
                methods: ['POST'],
                handler: this.handlePreview.bind(this),
                description: 'Preview navigation without full processing',
                rateLimit: true,
                timeout: 30000
            },
            '/api/navigate/options': {
                methods: ['GET'],
                handler: this.handleOptions.bind(this),
                description: 'Get available navigation options',
                rateLimit: false,
                timeout: 5000
            },
            '/api/navigate/schema': {
                methods: ['GET'],
                handler: this.handleSchema.bind(this),
                description: 'Get parameter schema documentation',
                rateLimit: false,
                timeout: 5000
            },
            '/api/navigate/health': {
                methods: ['GET'],
                handler: this.handleHealth.bind(this),
                description: 'Health check endpoint',
                rateLimit: false,
                timeout: 10000
            },
            '/api/navigate/metrics': {
                methods: ['GET'],
                handler: this.handleMetrics.bind(this),
                description: 'Get API metrics',
                rateLimit: false,
                timeout: 5000
            }
        };
    }

    /**
     * Initialize dependencies
     * @param {Object} dependencies - Required components
     */
    async initialize(dependencies = {}) {
        const { ragnoCorpus, sparqlStore, embeddingHandler, llmHandler } = dependencies;

        // Validate required dependencies
        await this.validateDependencies(dependencies);

        try {
            // Initialize components in sequence to avoid race conditions
            logger.info('Initializing NavigationEndpoint components in sequence...');

            // Initialize CorpuscleSelector first (core component)
            logger.debug('Initializing CorpuscleSelector...');
            this.corpuscleSelector = new CorpuscleSelector(ragnoCorpus, {
                sparqlStore,
                embeddingHandler,
                enableCaching: this.config.enableCaching
            });

            // Verify CorpuscleSelector is ready before proceeding
            if (typeof this.corpuscleSelector.initialize === 'function') {
                await this.corpuscleSelector.initialize();
            }

            // Initialize TiltProjector (depends on handlers)
            logger.debug('Initializing TiltProjector...');
            this.tiltProjector = new TiltProjector({
                embeddingHandler,
                textAnalyzer: llmHandler,
                graphAnalyzer: dependencies.graphAnalyzer,
                temporalAnalyzer: dependencies.temporalAnalyzer
            });

            // Initialize Transformer last (depends on others)
            logger.debug('Initializing CorpuscleTransformer...');
            this.transformer = new CorpuscleTransformer({
                enableCaching: this.config.enableCaching,
                enableMetrics: this.config.enableMetrics
            });

            // Verify all components are ready
            await this.verifyComponentsReady();

            logger.info('NavigationEndpoint initialized successfully');
        } catch (error) {
            logger.error('NavigationEndpoint initialization failed:', error);
            await this.cleanupPartialInitialization();
            throw new Error(`NavigationEndpoint initialization failed: ${error.message}`);
        }
    }

    /**
     * Main request handler - routes requests to appropriate endpoints
     * @param {Object} req - HTTP request object
     * @param {Object} res - HTTP response object
     */
    async handleRequest(req, res) {
        const requestId = this.generateRequestId();
        const startTime = Date.now();

        try {
            // Add CORS headers if enabled
            if (this.config.corsEnabled) {
                this.addCorsHeaders(res);
            }

            // Parse and validate request
            const parsedRequest = await this.requestParser.parse(req);
            const endpoint = this.matchEndpoint(parsedRequest.path, parsedRequest.method);

            if (!endpoint) {
                return this.errorHandler.handleError(
                    new Error('Endpoint not found'), 
                    req, 
                    res, 
                    { requestId, statusCode: 404 }
                );
            }

            // Check rate limits
            if (endpoint.rateLimit && this.config.enableRateLimit) {
                const rateLimitResult = this.checkRateLimit(parsedRequest.clientIp);
                if (!rateLimitResult.allowed) {
                    return this.errorHandler.handleError(
                        new Error('Rate limit exceeded'),
                        req,
                        res,
                        { requestId, statusCode: 429, rateLimitInfo: rateLimitResult }
                    );
                }
            }

            // Track active request
            this.trackActiveRequest(requestId, parsedRequest, startTime);

            // Execute endpoint handler with timeout
            const result = await this.executeWithTimeout(
                endpoint.handler,
                [parsedRequest, requestId],
                endpoint.timeout
            );

            // Format and send response
            const formattedResponse = await this.responseFormatter.format(result, {
                requestId,
                endpoint: parsedRequest.path,
                processingTime: Date.now() - startTime
            });

            this.sendSuccessResponse(res, formattedResponse);

            // Update metrics
            this.updateMetrics(parsedRequest.path, true, Date.now() - startTime);

        } catch (error) {
            logger.error('Request handling failed', { requestId, error: error.message });
            
            this.errorHandler.handleError(error, req, res, { requestId });
            this.updateMetrics(req.path || 'unknown', false, Date.now() - startTime);
        } finally {
            this.untrackActiveRequest(requestId);
        }
    }

    /**
     * Handle main navigation requests
     */
    async handleNavigate(parsedRequest, requestId) {
        logger.info('Processing navigation request', { requestId, params: parsedRequest.body });

        // Validate parameters
        const validationResult = this.validator.validate(parsedRequest.body);
        if (!validationResult.valid) {
            throw new Error(`Parameter validation failed: ${validationResult.message}`);
        }

        // Normalize parameters
        const normalizedParams = this.normalizer.normalize(parsedRequest.body);
        
        // Execute navigation pipeline
        const pipelineResult = await this.executeNavigationPipeline(
            normalizedParams, 
            requestId
        );

        return {
            success: true,
            requestId,
            navigation: pipelineResult.navigation,
            content: pipelineResult.content,
            metadata: pipelineResult.metadata,
            diagnostics: this.config.enableMetrics ? pipelineResult.diagnostics : undefined
        };
    }

    /**
     * Handle preview requests (limited processing)
     */
    async handlePreview(parsedRequest, requestId) {
        logger.info('Processing preview request', { requestId });

        // Validate parameters
        const validationResult = this.validator.validate(parsedRequest.body);
        if (!validationResult.valid) {
            throw new Error(`Parameter validation failed: ${validationResult.message}`);
        }

        // Normalize parameters
        const normalizedParams = this.normalizer.normalize(parsedRequest.body);

        // Execute selection only (no transformation)
        const selectionResult = await this.corpuscleSelector.select(normalizedParams);

        return {
            success: true,
            requestId,
            preview: true,
            navigation: normalizedParams,
            summary: {
                corpuscleCount: selectionResult.corpuscles.length,
                selectionTime: selectionResult.metadata.selectionTime,
                estimatedTokens: this.estimateTokensForCorpuscles(selectionResult.corpuscles),
                complexity: selectionResult.metadata.complexity
            },
            corpuscles: selectionResult.corpuscles.slice(0, 5) // Preview first 5
        };
    }

    /**
     * Handle options requests
     */
    async handleOptions(parsedRequest, requestId) {
        return {
            success: true,
            requestId,
            options: {
                zoomLevels: ['entity', 'unit', 'text', 'community', 'corpus'],
                tiltRepresentations: ['embedding', 'keywords', 'graph', 'temporal'],
                outputFormats: this.transformer ? 
                    Object.keys(this.transformer.promptFormatter.formats) : 
                    ['json', 'markdown', 'structured'],
                encodingStrategies: this.transformer ? 
                    Object.keys(this.transformer.metadataEncoder.encodingStrategies) : 
                    ['structured', 'compact', 'inline'],
                maxTokenLimits: {
                    'gpt-4': 128000,
                    'gpt-3.5-turbo': 16385,
                    'claude-3-opus': 200000,
                    'claude-3-sonnet': 200000
                },
                supportedTokenizers: ['cl100k_base', 'p50k_base', 'claude', 'llama']
            }
        };
    }

    /**
     * Handle schema requests
     */
    async handleSchema(parsedRequest, requestId) {
        return {
            success: true,
            requestId,
            schema: this.validator.getSchema(),
            examples: {
                basic: {
                    zoom: 'unit',
                    tilt: 'keywords',
                    transform: { maxTokens: 4000, 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'
                    }
                }
            },
            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)'
            }
        };
    }

    /**
     * Handle health check requests
     */
    async handleHealth(parsedRequest, requestId) {
        const healthChecks = {
            api: { status: 'healthy', timestamp: new Date().toISOString() },
            corpuscleSelector: null,
            tiltProjector: null,
            transformer: null,
            sparqlStore: null,
            embeddingHandler: null,
            llmHandler: null
        };

        // Check component health with timeout
        const checkPromises = [];

        // Check CorpuscleSelector
        if (this.corpuscleSelector) {
            checkPromises.push(
                this.performHealthCheck('corpuscleSelector', async () => {
                    const metrics = this.corpuscleSelector.getMetrics();
                    return {
                        status: 'healthy',
                        metrics: {
                            totalSelections: metrics.totalSelections,
                            avgSelectionTime: metrics.avgSelectionTime,
                            cacheHitRate: metrics.cacheHitRate
                        }
                    };
                })
            );
        }

        // Check Transformer
        if (this.transformer) {
            checkPromises.push(
                this.performHealthCheck('transformer', async () => {
                    const health = await this.transformer.healthCheck();
                    return {
                        status: health.healthy ? 'healthy' : 'degraded',
                        issues: health.issues
                    };
                })
            );
        }

        // Check TiltProjector
        if (this.tiltProjector) {
            checkPromises.push(
                this.performHealthCheck('tiltProjector', async () => {
                    // Simple check - verify it has required methods
                    const hasRequiredMethods = typeof this.tiltProjector.project === 'function';
                    return {
                        status: hasRequiredMethods ? 'healthy' : 'unhealthy',
                        capabilities: ['project']
                    };
                })
            );
        }

        // Check external services via stored references
        if (this.corpuscleSelector?.sparqlStore) {
            checkPromises.push(
                this.performHealthCheck('sparqlStore', async () => {
                    const health = await this.corpuscleSelector.sparqlStore.healthCheck();
                    return {
                        status: health.healthy ? 'healthy' : 'unhealthy',
                        endpoint: health.endpoint,
                        error: health.error
                    };
                })
            );
        }

        if (this.corpuscleSelector?.embeddingHandler) {
            checkPromises.push(
                this.performHealthCheck('embeddingHandler', async () => {
                    // Test with simple embedding generation
                    try {
                        await this.corpuscleSelector.embeddingHandler.generateEmbedding('test', { enableFallbacks: false });
                        return { status: 'healthy', provider: 'available' };
                    } catch (error) {
                        return { status: 'unhealthy', error: error.message };
                    }
                })
            );
        }

        if (this.tiltProjector?.textAnalyzer) {
            checkPromises.push(
                this.performHealthCheck('llmHandler', async () => {
                    // Test with simple concept extraction
                    try {
                        await this.tiltProjector.textAnalyzer.extractConcepts('test text', { timeoutMs: 5000 });
                        return { status: 'healthy', provider: 'available' };
                    } catch (error) {
                        return { status: 'unhealthy', error: error.message };
                    }
                })
            );
        }

        // Execute all health checks concurrently
        const results = await Promise.allSettled(checkPromises);
        
        // Process results
        results.forEach((result) => {
            if (result.status === 'fulfilled') {
                const { name, health } = result.value;
                healthChecks[name] = health;
            }
        });

        const overallHealthy = Object.values(healthChecks)
            .filter(check => check !== null)
            .every(check => check.status === 'healthy');

        return {
            success: true,
            requestId,
            status: overallHealthy ? 'healthy' : 'degraded',
            checks: healthChecks,
            activeRequests: this.activeRequests.size,
            uptime: process.uptime()
        };
    }

    /**
     * Handle metrics requests
     */
    async handleMetrics(parsedRequest, requestId) {
        if (!this.config.enableMetrics) {
            throw new Error('Metrics collection is disabled');
        }

        return {
            success: true,
            requestId,
            metrics: {
                requests: this.requestMetrics,
                components: {
                    corpuscleSelector: this.corpuscleSelector?.getMetrics(),
                    transformer: this.transformer?.getMetrics()
                },
                system: {
                    activeRequests: this.activeRequests.size,
                    memoryUsage: process.memoryUsage(),
                    uptime: process.uptime()
                }
            }
        };
    }

    /**
     * Execute complete navigation pipeline
     */
    async executeNavigationPipeline(normalizedParams, requestId) {
        logger.debug('Executing navigation pipeline', { requestId });

        // Stage 1: Corpuscle Selection
        const selectionStart = Date.now();
        const selectionResult = await this.corpuscleSelector.select(normalizedParams);
        const selectionTime = Date.now() - selectionStart;

        logger.debug('Selection completed', { 
            requestId, 
            corpuscleCount: selectionResult.corpuscles.length,
            selectionTime 
        });

        // Stage 2: Tilt Projection
        const projectionStart = Date.now();
        const projectionResult = await this.tiltProjector.project(
            selectionResult.corpuscles,
            normalizedParams.tilt,
            {
                embeddingHandler: this.embeddingHandler,
                textAnalyzer: this.textAnalyzer
            }
        );
        const projectionTime = Date.now() - projectionStart;

        logger.debug('Projection completed', { 
            requestId, 
            representation: projectionResult.representation,
            projectionTime 
        });

        // Stage 3: Transformation
        const transformStart = Date.now();
        const transformResult = await this.transformer.transform(
            projectionResult,
            selectionResult,
            normalizedParams.transform
        );
        const transformTime = Date.now() - transformStart;

        logger.debug('Transformation completed', { 
            requestId, 
            format: transformResult.metadata?.output?.format,
            transformTime 
        });

        return {
            navigation: normalizedParams,
            content: transformResult.content,
            metadata: {
                pipeline: {
                    selectionTime,
                    projectionTime,
                    transformTime,
                    totalTime: selectionTime + projectionTime + transformTime
                },
                selection: selectionResult.metadata,
                projection: projectionResult.metadata,
                transformation: transformResult.metadata
            },
            diagnostics: {
                requestId,
                stages: ['selection', 'projection', 'transformation'],
                performance: {
                    selectionTime,
                    projectionTime,
                    transformTime
                },
                cacheUsage: {
                    selectionCached: selectionResult.metadata.fromCache,
                    transformationCached: transformResult.diagnostics?.cacheUsed
                }
            }
        };
    }

    /**
     * Request routing and validation
     */
    matchEndpoint(path, method) {
        const endpoint = this.endpoints[path];
        if (!endpoint) return null;
        
        if (!endpoint.methods.includes(method.toUpperCase())) return null;
        
        return endpoint;
    }

    /**
     * Rate limiting
     */
    checkRateLimit(clientIp) {
        const now = Date.now();
        const windowMs = this.config.rateLimit.windowMs;
        const maxRequests = this.config.rateLimit.requests;

        if (!this.rateLimitStore.has(clientIp)) {
            this.rateLimitStore.set(clientIp, {
                requests: 1,
                resetTime: now + windowMs
            });
            return { allowed: true, remaining: maxRequests - 1 };
        }

        const clientData = this.rateLimitStore.get(clientIp);
        
        if (now > clientData.resetTime) {
            // Reset window
            clientData.requests = 1;
            clientData.resetTime = now + windowMs;
            return { allowed: true, remaining: maxRequests - 1 };
        }

        if (clientData.requests >= maxRequests) {
            return { 
                allowed: false, 
                remaining: 0,
                resetTime: clientData.resetTime 
            };
        }

        clientData.requests++;
        return { 
            allowed: true, 
            remaining: maxRequests - clientData.requests 
        };
    }

    /**
     * Request tracking
     */
    trackActiveRequest(requestId, parsedRequest, startTime) {
        this.activeRequests.set(requestId, {
            path: parsedRequest.path,
            method: parsedRequest.method,
            startTime,
            clientIp: parsedRequest.clientIp
        });

        // Cleanup old requests periodically
        if (this.activeRequests.size % 50 === 0) {
            this.cleanupOldRequests();
        }
    }

    untrackActiveRequest(requestId) {
        this.activeRequests.delete(requestId);
    }

    cleanupOldRequests() {
        const now = Date.now();
        const timeout = this.config.requestTimeoutMs;

        for (const [requestId, request] of this.activeRequests.entries()) {
            if (now - request.startTime > timeout) {
                this.activeRequests.delete(requestId);
            }
        }
    }

    /**
     * Metrics tracking
     */
    updateMetrics(endpoint, success, responseTime) {
        this.requestMetrics.totalRequests++;
        
        if (success) {
            this.requestMetrics.successfulRequests++;
        } else {
            this.requestMetrics.failedRequests++;
        }

        // Update average response time
        const totalRequests = this.requestMetrics.totalRequests;
        this.requestMetrics.avgResponseTime = 
            (this.requestMetrics.avgResponseTime * (totalRequests - 1) + responseTime) / totalRequests;

        // Track by endpoint
        if (!this.requestMetrics.requestsByEndpoint.has(endpoint)) {
            this.requestMetrics.requestsByEndpoint.set(endpoint, { total: 0, successful: 0 });
        }
        
        const endpointStats = this.requestMetrics.requestsByEndpoint.get(endpoint);
        endpointStats.total++;
        if (success) endpointStats.successful++;
    }

    /**
     * Utility methods
     */
    executeWithTimeout(handler, args, timeoutMs) {
        return new Promise((resolve, reject) => {
            const timeout = setTimeout(() => {
                reject(new Error(`Request timed out after ${timeoutMs}ms`));
            }, timeoutMs);

            handler(...args)
                .then(result => {
                    clearTimeout(timeout);
                    resolve(result);
                })
                .catch(error => {
                    clearTimeout(timeout);
                    reject(error);
                });
        });
    }

    addCorsHeaders(res) {
        res.setHeader('Access-Control-Allow-Origin', '*');
        res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
        res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
        res.setHeader('Access-Control-Max-Age', '86400');
    }

    sendSuccessResponse(res, data) {
        res.setHeader('Content-Type', 'application/json');
        res.statusCode = 200;
        res.end(JSON.stringify(data, null, 2));
    }

    generateRequestId() {
        return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    }

    estimateTokensForCorpuscles(corpuscles) {
        // Simple estimation for preview
        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);
    }

    /**
     * Configuration and management
     */
    getEndpointInfo() {
        return Object.entries(this.endpoints).map(([path, config]) => ({
            path,
            methods: config.methods,
            description: config.description,
            rateLimit: config.rateLimit,
            timeout: config.timeout
        }));
    }

    updateConfig(newConfig) {
        Object.assign(this.config, newConfig);
        logger.info('NavigationEndpoint configuration updated');
    }

    /**
     * Validate dependencies before initialization
     */
    async validateDependencies(dependencies) {
        const required = ['ragnoCorpus', 'sparqlStore', 'embeddingHandler', 'llmHandler'];
        const missing = required.filter(dep => !dependencies[dep]);
        
        if (missing.length > 0) {
            throw new Error(`Missing required dependencies: ${missing.join(', ')}`);
        }

        // Check if dependencies are healthy/ready
        const healthChecks = await Promise.allSettled([
            this.checkDependencyHealth('sparqlStore', dependencies.sparqlStore),
            this.checkDependencyHealth('embeddingHandler', dependencies.embeddingHandler),
            this.checkDependencyHealth('llmHandler', dependencies.llmHandler)
        ]);

        const failures = healthChecks
            .map((result, index) => ({ dep: ['sparqlStore', 'embeddingHandler', 'llmHandler'][index], result }))
            .filter(({ result }) => result.status === 'rejected');

        if (failures.length > 0) {
            logger.warn('Some dependencies failed health checks:', failures.map(f => f.dep));
            // Don't fail initialization, but log warnings
        }
    }

    /**
     * Check if a dependency is healthy
     */
    async checkDependencyHealth(name, dependency) {
        try {
            if (typeof dependency.healthCheck === 'function') {
                const health = await Promise.race([
                    dependency.healthCheck(),
                    new Promise((_, reject) => setTimeout(() => reject(new Error('Health check timeout')), 5000))
                ]);
                
                if (health && !health.healthy) {
                    throw new Error(`${name} is not healthy: ${health.error}`);
                }
            }
            return { healthy: true };
        } catch (error) {
            logger.warn(`Health check failed for ${name}:`, error.message);
            throw error;
        }
    }

    /**
     * Perform a health check with timeout
     */
    async performHealthCheck(name, healthCheckFn) {
        try {
            const health = await Promise.race([
                healthCheckFn(),
                new Promise((_, reject) => 
                    setTimeout(() => reject(new Error('Health check timeout')), 5000)
                )
            ]);
            
            return { name, health };
        } catch (error) {
            return { 
                name, 
                health: { 
                    status: 'unhealthy', 
                    error: error.message 
                } 
            };
        }
    }

    /**
     * Verify all components are ready after initialization
     */
    async verifyComponentsReady() {
        const components = [
            { name: 'corpuscleSelector', instance: this.corpuscleSelector },
            { name: 'tiltProjector', instance: this.tiltProjector },
            { name: 'transformer', instance: this.transformer }
        ];

        for (const { name, instance } of components) {
            if (!instance) {
                throw new Error(`Component ${name} was not initialized`);
            }

            // Check if component has a ready check method
            if (typeof instance.isReady === 'function') {
                const ready = await instance.isReady();
                if (!ready) {
                    throw new Error(`Component ${name} is not ready`);
                }
            }
        }

        logger.debug('All components verified ready');
    }

    /**
     * Cleanup partial initialization on failure
     */
    async cleanupPartialInitialization() {
        logger.debug('Cleaning up partial initialization...');

        const components = [
            { name: 'transformer', instance: this.transformer },
            { name: 'tiltProjector', instance: this.tiltProjector },
            { name: 'corpuscleSelector', instance: this.corpuscleSelector }
        ];

        for (const { name, instance } of components) {
            if (instance && typeof instance.dispose === 'function') {
                try {
                    await instance.dispose();
                    logger.debug(`Cleaned up ${name}`);
                } catch (error) {
                    logger.warn(`Failed to cleanup ${name}:`, error.message);
                }
            }
        }

        // Reset component references
        this.corpuscleSelector = null;
        this.tiltProjector = null;
        this.transformer = null;
    }

    async shutdown() {
        logger.info('Shutting down NavigationEndpoint');
        
        // 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) {
            logger.warn(`Forced shutdown with ${this.activeRequests.size} active requests`);
        }

        // Cleanup components in reverse order of initialization
        logger.debug('Cleaning up components...');
        
        const components = [
            { name: 'transformer', instance: this.transformer },
            { name: 'tiltProjector', instance: this.tiltProjector },
            { name: 'corpuscleSelector', instance: this.corpuscleSelector }
        ];

        for (const { name, instance } of components) {
            if (instance) {
                try {
                    if (typeof instance.dispose === 'function') {
                        await instance.dispose();
                        logger.debug(`Disposed ${name}`);
                    } else if (typeof instance.shutdown === 'function') {
                        await instance.shutdown();
                        logger.debug(`Shutdown ${name}`);
                    } else {
                        logger.debug(`${name} has no cleanup method, skipping`);
                    }
                } catch (error) {
                    logger.warn(`Failed to cleanup ${name}:`, error.message);
                }
            }
        }

        // Clean up resources
        logger.debug('Cleaning up internal resources...');
        this.rateLimitStore.clear();
        this.activeRequests.clear();
        
        // Reset component references
        this.corpuscleSelector = null;
        this.tiltProjector = null;
        this.transformer = null;
        
        logger.info('NavigationEndpoint shutdown complete');
    }
}