import {MessageRangesHandler} from './MessageRangesHandler';

interface PendingMessage {
  channel: string;
  ts: string;
}

/**
 * Tracks messages that are currently being processed (in-flight).
 * This prevents the MessageRangesHandler from extending ranges past messages
 * that haven't been confirmed as saved yet.
 *
 * Flow:
 * 1. Message arrives → registerPending(conversationId, ts)
 * 2. Message saved to DB successfully → confirmSaved(conversationId, ts)
 * 3. Only then can the range be safely extended past this ts
 */
class PendingMessagesTrackerClass {
  // Map of conversationId -> Set of pending message timestamps
  private pendingMessages: Map<string, Set<string>> = new Map();

  // Map of conversationId -> ts[] for messages that were confirmed but range wasn't updated yet
  private confirmedAwaitingRangeUpdate: Map<string, string[]> = new Map();

  /**
   * Register a message as pending (in-flight, not yet saved to DB)
   */
  public registerPending(conversationId: string, ts: string): void {
    const pending = this.pendingMessages.get(conversationId) ?? new Set();
    pending.add(ts);
    this.pendingMessages.set(conversationId, pending);
  }

  /**
   * Confirm that a message was successfully saved to DB
   */
  public confirmSaved(conversationId: string, ts: string): void {
    const pending = this.pendingMessages.get(conversationId);
    if (pending) {
      pending.delete(ts);
      if (pending.size === 0) {
        this.pendingMessages.delete(conversationId);
      }
    }

    // Add to confirmed list for potential range update
    const confirmed =
      this.confirmedAwaitingRangeUpdate.get(conversationId) ?? [];
    confirmed.push(ts);
    this.confirmedAwaitingRangeUpdate.set(conversationId, confirmed);
  }

  /**
   * Mark a message as failed (remove from pending without confirming)
   */
  public markFailed(conversationId: string, ts: string): void {
    const pending = this.pendingMessages.get(conversationId);
    if (pending) {
      pending.delete(ts);
      if (pending.size === 0) {
        this.pendingMessages.delete(conversationId);
      }
    }
  }

  /**
   * Check if there are any pending messages with ts less than the given ts
   * This is used to prevent extending the range past unconfirmed messages
   */
  public hasPendingMessagesBefore(conversationId: string, ts: string): boolean {
    const pending = this.pendingMessages.get(conversationId);
    if (!pending || pending.size === 0) {
      return false;
    }

    for (const pendingTs of pending) {
      if (pendingTs < ts) {
        return true;
      }
    }

    return false;
  }

  /**
   * Get all pending message timestamps for a conversation
   */
  public getPending(conversationId: string): string[] {
    const pending = this.pendingMessages.get(conversationId);
    return pending ? Array.from(pending) : [];
  }

  /**
   * Get and clear confirmed messages awaiting range update
   */
  public getAndClearConfirmed(conversationId: string): string[] {
    const confirmed =
      this.confirmedAwaitingRangeUpdate.get(conversationId) ?? [];
    this.confirmedAwaitingRangeUpdate.delete(conversationId);
    return confirmed;
  }

  /**
   * Clear all pending messages for a conversation
   */
  public clearConversation(conversationId: string): void {
    this.pendingMessages.delete(conversationId);
    this.confirmedAwaitingRangeUpdate.delete(conversationId);
  }

  /**
   * Confirm messages as saved and update ranges accordingly.
   * This is called after messages are successfully saved to DB.
   * Groups messages by channel and updates ranges for each.
   */
  public confirmMessagesAndUpdateRanges(messages: PendingMessage[]): void {
    // Group messages by channel
    const messagesByChannel = new Map<string, PendingMessage[]>();
    for (const msg of messages) {
      const channelMessages = messagesByChannel.get(msg.channel) ?? [];
      channelMessages.push(msg);
      messagesByChannel.set(msg.channel, channelMessages);
    }

    // Process each channel
    for (const [channelId, channelMessages] of messagesByChannel) {
      // Confirm each message as saved
      for (const msg of channelMessages) {
        this.confirmSaved(channelId, msg.ts);
      }

      // Get all confirmed messages that were waiting for range update
      const confirmed = this.getAndClearConfirmed(channelId);

      if (confirmed.length > 0) {
        // Find the live range to get bridge info
        const ranges = MessageRangesHandler.get(channelId);
        const liveRange = ranges.find(r => r.isEnd);

        // Update ranges with confirmed messages (skipPendingCheck since already confirmed)
        MessageRangesHandler.addMessagesAndUpdateRanges(
          channelId,
          confirmed.map(ts => ({ts})),
          {
            isStart: false,
            isEnd: true,
            bridgeOldestTs: liveRange?.newestTs,
            skipPendingCheck: true,
          },
        );
      }
    }
  }

  /**
   * Mark multiple messages as failed.
   * This is called when DB save fails for a batch of messages.
   */
  public markMessagesFailed(messages: PendingMessage[]): void {
    for (const msg of messages) {
      this.markFailed(msg.channel, msg.ts);
    }
  }
}

export const PendingMessagesTracker = new PendingMessagesTrackerClass();
