import type { MessageReceiveEvent } from "./client.js";

export interface QueuedTrigger {
  readonly projectId: string;
  readonly chatId: string;
  readonly prompt: string;
  /** Role frozen when the message was accepted; later group switches cannot change it. */
  readonly roleId: string;
  readonly executionKind: "conversation" | "native_compact";
  readonly msg: MessageReceiveEvent["message"];
  readonly senderOpenId: string;
  readonly actor: { readonly feishuOpenId: string; readonly chatId: string };
  readonly enqueuedAt: number;
}

export interface TriggerQueueOptions {
  readonly maxQueueSize?: number;
  readonly maxWaitMs?: number;
}

const DEFAULT_MAX_QUEUE_SIZE = 5;
const DEFAULT_MAX_WAIT_MS = 300_000;

export class TriggerQueue {
  readonly maxQueueSize: number;
  readonly maxWaitMs: number;

  private readonly queues = new Map<string, QueuedTrigger[]>();

  constructor(options: TriggerQueueOptions = {}) {
    this.maxQueueSize = Math.max(0, Math.trunc(options.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE));
    this.maxWaitMs = Math.max(0, Math.trunc(options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS));
  }

  enqueue(projectId: string, trigger: Omit<QueuedTrigger, "projectId" | "enqueuedAt">): number {
    const queue = this.queues.get(projectId) ?? [];
    if (queue.length >= this.maxQueueSize) return 0;

    queue.push({
      ...trigger,
      projectId,
      enqueuedAt: Date.now(),
    });
    if (!this.queues.has(projectId)) {
      this.queues.set(projectId, queue);
    }
    return queue.length;
  }

  dequeue(projectId: string): QueuedTrigger | null {
    const queue = this.queues.get(projectId);
    if (queue === undefined || queue.length === 0) return null;

    const next = queue.shift() ?? null;
    if (queue.length === 0) {
      this.queues.delete(projectId);
    }
    return next;
  }

  peek(projectId: string): QueuedTrigger | null {
    return this.queues.get(projectId)?.[0] ?? null;
  }

  length(projectId: string): number {
    return this.queues.get(projectId)?.length ?? 0;
  }

  hasPending(projectId: string): boolean {
    return this.length(projectId) > 0;
  }

  purgeExpired(): number {
    const now = Date.now();
    let removed = 0;
    for (const [projectId, queue] of this.queues) {
      const fresh = queue.filter((trigger) => !this.isExpired(trigger, now));
      removed += queue.length - fresh.length;
      if (fresh.length === 0) {
        this.queues.delete(projectId);
      } else if (fresh.length !== queue.length) {
        this.queues.set(projectId, fresh);
      }
    }
    return removed;
  }

  clear(projectId: string): number {
    const count = this.length(projectId);
    this.queues.delete(projectId);
    return count;
  }

  clearAll(): void {
    this.queues.clear();
  }

  isExpired(trigger: QueuedTrigger, now = Date.now()): boolean {
    return trigger.enqueuedAt + this.maxWaitMs < now;
  }
}

export const triggerQueue = new TriggerQueue();
