/**
 * Outgoing-queue persistence interface.
 *
 * The client buffers ciphertext blobs until the inbox server confirms the
 * PUT. If the process restarts mid-flush, the queue must survive — so we
 * model it as an explicit interface that apps can back with SQLite, IndexedDB,
 * AsyncStorage, etc.
 *
 * Each entry is keyed by `(recipientAddress, msgId)` (the same key the
 * server uses) so retries are naturally idempotent.
 */
export interface OutgoingEntry {
  /** Recipient address (the inbox owner). */
  recipientAddress: string;
  /** Hex SHA-256 of `ciphertext` — server-side msg id. */
  msgId: string;
  /** Wire-encoded ShadeEnvelope to deliver. */
  ciphertext: Uint8Array;
  /** Time-to-live in seconds. The server clamps to its allowed range. */
  ttlSeconds: number;
  /** Local timestamp when the entry was queued (ms). */
  queuedAt: number;
  /** Number of failed delivery attempts so far. */
  attempts: number;
}

export interface OutgoingQueueStore {
  /** Insert a new entry. Idempotent on (recipientAddress, msgId). */
  enqueue(entry: OutgoingEntry): Promise<void>;
  /** List entries in insertion order. Caller filters/limits. */
  list(): Promise<OutgoingEntry[]>;
  /** Remove an entry by composite key. Returns true if found. */
  remove(recipientAddress: string, msgId: string): Promise<boolean>;
  /** Update the attempts counter for an entry. */
  bumpAttempts(recipientAddress: string, msgId: string): Promise<void>;
  /** Total queued count. */
  size(): Promise<number>;
}

export class MemoryOutgoingQueueStore implements OutgoingQueueStore {
  private entries: OutgoingEntry[] = [];

  async enqueue(entry: OutgoingEntry): Promise<void> {
    const dup = this.entries.some(
      (e) => e.recipientAddress === entry.recipientAddress && e.msgId === entry.msgId,
    );
    if (dup) return;
    this.entries.push({ ...entry, ciphertext: new Uint8Array(entry.ciphertext) });
  }

  async list(): Promise<OutgoingEntry[]> {
    return this.entries.map((e) => ({
      ...e,
      ciphertext: new Uint8Array(e.ciphertext),
    }));
  }

  async remove(recipientAddress: string, msgId: string): Promise<boolean> {
    const idx = this.entries.findIndex(
      (e) => e.recipientAddress === recipientAddress && e.msgId === msgId,
    );
    if (idx === -1) return false;
    this.entries.splice(idx, 1);
    return true;
  }

  async bumpAttempts(recipientAddress: string, msgId: string): Promise<void> {
    const e = this.entries.find(
      (x) => x.recipientAddress === recipientAddress && x.msgId === msgId,
    );
    if (e) e.attempts++;
  }

  async size(): Promise<number> {
    return this.entries.length;
  }
}
