import type { DetectionConfig, DetectionResult, DetectionMetrics, WorkerMessage, WorkerResponse } from './types';
import { ModelCache } from './model-cache';
import { MODEL_VARIANTS } from './model-config';

/**
 * Manages the detection worker and handles communication
 */
export class DetectionWorkerManager {
  private worker: Worker | null = null;
  private messageId = 0;
  private pendingMessages = new Map<string, { resolve: (value: unknown) => void; reject: (reason?: unknown) => void }>();
  private modelCache = new ModelCache();
  private isWorkerReady = false;

  constructor() {
    this.initializeWorker();
  }

  private async initializeWorker() {
    try {
      // Create worker from the detection worker file
      this.worker = new Worker(
        new URL('../../workers/detection-worker.ts', import.meta.url),
        { type: 'module' }
      );

      this.worker.onmessage = (event: MessageEvent<WorkerResponse>) => {
        this.handleWorkerMessage(event.data);
      };

      this.worker.onerror = (error) => {
        console.error('Worker error:', error);
        this.isWorkerReady = false;
      };

      this.isWorkerReady = true;
      console.log('Detection worker initialized');
      await this.sendMessage('INITIALIZE', undefined);

    } catch (error) {
      console.error('Failed to initialize worker:', error);
      this.isWorkerReady = false;
    }
  }

  private handleWorkerMessage(message: WorkerResponse) {
    const { type, id } = message;
    
    const pending = this.pendingMessages.get(id);
    if (!pending) {
      console.warn('Received response for unknown message ID:', id);
      return;
    }

    this.pendingMessages.delete(id);

    if (type === 'ERROR') {
      pending.reject(new Error((message as { error: string }).error));
    } else if (type === 'DETECTION_RESULT') {
      const detectionMessage = message as { result: DetectionResult | null };
      pending.resolve({ result: detectionMessage.result });
    } else if (type === 'INITIALIZED') {
      pending.resolve(undefined);
    } else if (type === 'METRICS_UPDATE') {
      pending.resolve({ metrics: (message as { metrics: Partial<DetectionMetrics> }).metrics });
    } else if (type === 'LOADED_MODEL') {
      pending.resolve(undefined);
    } else if (type === 'CONFIGURED') {
      pending.resolve(undefined);
    } else {
      pending.resolve(message);
    }
  }

  private async sendMessage<T>(type: WorkerMessage['type'], payload: unknown): Promise<T> {
    if (!this.worker || !this.isWorkerReady) {
      throw new Error('Worker not available');
    }

    const id = (this.messageId++).toString();

    return new Promise((resolve, reject) => {
      this.pendingMessages.set(id, { resolve, reject });
      
      let message: WorkerMessage & { id: string };
      
      if (type === 'INITIALIZE') {
        message = { type, id };
      } else if (type === 'DETECT') {
        message = { type, imageData: payload.imageData, id };
      } else if (type === 'UPDATE_CONFIG' || type === 'CONFIGURE') {
        message = { type, config: payload, id };
      } else if (type === 'LOAD_MODEL') {
        message = { type, variant: payload.variant, modelData: payload.modelData, id };
      } else {
        throw new Error(`Unknown message type for sendMessage: ${type}`);
      }

      this.worker!.postMessage(message);

      // Timeout after 30 seconds
      setTimeout(() => {
        if (this.pendingMessages.has(id)) {
          this.pendingMessages.delete(id);
          reject(new Error('Worker message timeout'));
        }
      }, 90000);
    });
  }

  /**
   * Load a model into the worker
   */
  async loadModel(variant: 'quantized' | 'standard' | 'full', onProgress?: (progress: number) => void): Promise<void> {
    const modelInfo = MODEL_VARIANTS[variant];
    
    try {
      // Get model data from cache or download
      const modelData = await this.modelCache.getModel(variant, modelInfo, onProgress);
      
      // Send model data to worker
      await this.sendMessage('LOAD_MODEL', {
        variant,
        modelData
      });

      console.log(`Model ${variant} loaded successfully`);
      
    } catch (error) {
      console.error(`Failed to load model ${variant}:`, error);
      throw error;
    }
  }

  /**
   * Configure the detection settings
   */
  async configure(config: DetectionConfig): Promise<void> {
    await this.sendMessage('CONFIGURE', config);
  }

  /**
   * Perform detection on an image
   */
  async detect(imageData: ImageData): Promise<DetectionResult[]> {
    if (!this.isWorkerReady) {
      throw new Error('Worker not ready');
    }

    try {
      const results = await this.sendMessage<{ result: DetectionResult | null }>('DETECT', { imageData });
      
      // Handle the case where results or results.result is undefined
      if (!results || results.result === undefined || results.result === null) {
        return [];
      }
      
      return [results.result];
    } catch (error) {
      console.error('Detection failed:', error);
      throw error;
    }
  }

  /**
   * Get worker metrics
   */
  async getMetrics(): Promise<object> {
    return await this.sendMessage('getMetrics', {});
  }

  /**
   * Check if worker is ready
   */
  isReady(): boolean {
    return this.isWorkerReady;
  }

  /**
   * Terminate the worker
   */
  destroy() {
    if (this.worker) {
      this.worker.terminate();
      this.worker = null;
      this.isWorkerReady = false;
    }
    
    // Reject all pending messages
    this.pendingMessages.forEach(({ reject }) => {
      reject(new Error('Worker terminated'));
    });
    this.pendingMessages.clear();
  }
}

/**
 * Singleton instance manager
 */
let workerManager: DetectionWorkerManager | null = null;

export function getDetectionWorkerManager(): DetectionWorkerManager {
  if (!workerManager) {
    workerManager = new DetectionWorkerManager();
  }
  return workerManager;
}

export function destroyDetectionWorkerManager() {
  if (workerManager) {
    workerManager.destroy();
    workerManager = null;
  }
}