/**
 * 消息发送队列（限流）
 *
 * 业务行为：
 *   - 队列空闲时，第一条消息立即发送
 *   - 后续消息在冷却窗口内暂存，并按 `intervalMs` 合并成一条发送
 *   - 队列只负责发送节奏，不关心每批消息是否成功消费
 *   - 队列可在任意时刻清空（dispose）
 */
import { Logger } from "@/utils";

export type MessageConsumer = (content: string) => void | Promise<void>;

export interface MessageQueueOptions {
  intervalMs?: number;
  joiner?: string;
}

const log = new Logger("MessageQueue");

export class MessageQueue {
  private pending: string[] = [];
  private timer: ReturnType<typeof setTimeout> | null = null;
  private sending = false;
  private disposed = false;
  private consumer: MessageConsumer | null = null;
  private readonly intervalMs: number;
  private readonly joiner: string;

  constructor(options: MessageQueueOptions | number = 1000) {
    if (typeof options === "number") {
      this.intervalMs = options;
      this.joiner = "\n";
      return;
    }

    this.intervalMs = options.intervalMs ?? 1000;
    this.joiner = options.joiner ?? "\n";
  }

  /** 注册消费者。队列只负责调用，不根据结果做重试。 */
  setConsumer(consumer: MessageConsumer): void {
    this.consumer = consumer;
  }

  enqueue(content: string): void {
    if (this.disposed) return;
    if (!this.sending && !this.timer && this.pending.length === 0) {
      log.debug("send immediate", { contentLength: content.length });
      void this.sendBatch(content);
      return;
    }

    this.pending.push(content);
    log.debug("enqueue pending", {
      pendingCount: this.pending.length,
      contentLength: content.length,
    });
  }

  private async sendBatch(content: string): Promise<void> {
    if (this.disposed) return;
    this.sending = true;
    this.scheduleFlush();
    if (!this.consumer) {
      this.pending.unshift(content);
      this.sending = false;
      this.scheduleFlush();
      return;
    }

    try {
      await this.consumer(content);
    } catch (e) {
      log.error({ err: e }, "consumer error");
    }

    this.sending = false;
    this.scheduleFlush();
  }

  private scheduleFlush(): void {
    if (this.disposed || this.timer) return;
    this.timer = setTimeout(() => {
      this.timer = null;
      void this.flushPending();
    }, this.intervalMs);
  }

  private async flushPending(): Promise<void> {
    if (this.disposed) return;
    if (this.sending) {
      this.scheduleFlush();
      return;
    }

    const batch = this.pending.splice(0, this.pending.length);
    if (batch.length === 0) return;

    const content = batch.join(this.joiner);
    log.debug("flush batch", {
      batchCount: batch.length,
      contentLength: content.length,
    });
    await this.sendBatch(content);
  }

  clear(): void {
    this.pending = [];
  }

  dispose(): void {
    this.disposed = true;
    this.clear();
    if (this.timer) {
      clearTimeout(this.timer);
      this.timer = null;
    }
  }
}
