/**
 * Copyright 2026 Reto Meier
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import WebSocket from 'ws';

interface ProgressQueueItem {
  id: number;
  resolved: boolean;
  value?: string;
  error?: boolean;
  addedAt: number;
}

export class ProgressQueue {
  private queue: ProgressQueueItem[] = [];
  private idCounter = 0;

  constructor(
    private ws: WebSocket,
    private clientUUID: string
  ) {}

  add(message: string | Promise<string>) {
    const item: ProgressQueueItem = {
      id: this.idCounter++,
      resolved: false,
      addedAt: Date.now(),
    };
    
    this.queue.push(item);

    if (typeof message === 'string') {
      item.resolved = true;
      item.value = message;
      this.process();
    } else {
      // It's a Promise
      message
        .then(val => {
          item.resolved = true;
          item.value = val;
          queueMicrotask(() => this.process());
        })
        .catch(err => {
          console.error(`Progress update promise failed:`, err);
          item.error = true;
          // Capture the error message to send to the client
          item.value = err instanceof Error ? err.message : String(err);
          queueMicrotask(() => this.process());
        });

      // Add a 50ms buffer to ensure Date.now() - addedAt is strictly >= 30000
      setTimeout(() => this.process(), 30050);
    }
  }

  process() {
    if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;

    while (this.queue.length > 0) {
      const head = this.queue[0];
      
      // 1. Handle Rejected Promises
      if (head.error) {
        try {
          this.ws.send(JSON.stringify({
            status: 'PROGRESS_UPDATES',
            completed_status_message: `Update failed: ${head.value}`,
          }));
        } catch (e) {
          console.error('Failed to send error update, will retry:', e);
          break; // Pause queue if socket throws an error
        }
        
        this.queue.shift();
        continue;
      }

      // 2. Handle Successfully Resolved Promises / Strings
      if (head.resolved) {
        try {
          this.ws.send(JSON.stringify({
            status: 'PROGRESS_UPDATES',
            completed_status_message: head.value,
            isError: false
          }));
        } catch (e) {
          console.error('Failed to send progress update, will retry:', e);
          break; 
        }
        
        this.queue.shift();
        continue;
      }

      // 3. Handle Timeouts
      const timeInQueue = Date.now() - head.addedAt;
      // Allow timeout if a subsequent item is resolved OR errored (both mean the future moved on)
      const hasFinishedSubsequent = this.queue.slice(1).some(q => q.resolved || q.error);

      if (timeInQueue >= 29900 && hasFinishedSubsequent) {
        console.log(`Sending timeout payload for unresolved progress update (client ${this.clientUUID}).`);
        
        try {
          this.ws.send(JSON.stringify({
            status: 'PROGRESS_UPDATES',
            completed_status_message: `Update failed: Operation timed out after 30 seconds`,
          }));
        } catch (e) {
          console.error('Failed to send timeout update, will retry:', e);
          break;
        }

        this.queue.shift(); 
        continue;
      }

      // The head is unresolved and doesn't meet the skip criteria yet. Block the queue.
      break; 
    }
  }
}