class BoundedQueue {
  constructor(options = {}) {
    this.maxItems = positive(options.maxItems, 250);
    this.maxBytes = positive(options.maxBytes, 5 * 32000);
    this.maxAgeMs = positive(options.maxAgeMs, 5000);
    this.now = options.now || Date.now;
    this.items = [];
    this.bytes = 0;
    this.dropped = { capacity: 0, stale: 0 };
  }

  push(value, options = {}) {
    const bytes = Math.max(0, Number(options.bytes ?? value?.length ?? value?.pcm?.length ?? 0));
    const capturedAt = Number(options.capturedAt ?? this.now());
    if (bytes > this.maxBytes) {
      this.dropped.capacity += 1;
      return false;
    }
    this.prune();
    while (this.items.length >= this.maxItems || this.bytes + bytes > this.maxBytes) this.dropOldest("capacity");
    this.items.push({ value, bytes, capturedAt });
    this.bytes += bytes;
    return true;
  }

  shift() {
    this.prune();
    const entry = this.items.shift();
    if (!entry) return null;
    this.bytes -= entry.bytes;
    return entry.value;
  }

  prune() {
    const cutoff = this.now() - this.maxAgeMs;
    while (this.items[0] && this.items[0].capturedAt < cutoff) this.dropOldest("stale");
  }

  clear() { this.items.length = 0; this.bytes = 0; }
  size() { this.prune(); return this.items.length; }
  metrics() { this.prune(); return { items: this.items.length, bytes: this.bytes, dropped: { ...this.dropped } }; }

  dropOldest(reason) {
    const entry = this.items.shift();
    if (!entry) return;
    this.bytes -= entry.bytes;
    this.dropped[reason] += 1;
  }
}

class SequenceTracker {
  constructor() { this.last = null; this.gaps = 0; this.outOfOrder = 0; }
  accept(sequence) {
    const current = Number(sequence) >>> 0;
    if (this.last == null) { this.last = current; return { accepted: true, gap: 0 }; }
    if (current <= this.last) { this.outOfOrder += 1; return { accepted: false, gap: 0 }; }
    const gap = current - this.last - 1;
    if (gap) this.gaps += gap;
    this.last = current;
    return { accepted: true, gap };
  }
  metrics() { return { last_sequence: this.last, sequence_gaps: this.gaps, out_of_order: this.outOfOrder }; }
}

class RollingPcmBuffer {
  constructor(options = {}) {
    this.queue = new BoundedQueue({ maxItems: options.maxItems || 300, maxBytes: (options.seconds || 5) * 32000, maxAgeMs: (options.seconds || 5) * 1000, now: options.now });
  }
  push(frame, capturedAt) { return this.queue.push(Buffer.from(frame), { bytes: frame.length, capturedAt }); }
  snapshot() { this.queue.prune(); return Buffer.concat(this.queue.items.map((entry) => entry.value), this.queue.bytes); }
  metrics() { return this.queue.metrics(); }
  clear() { this.queue.clear(); }
}

function positive(value, fallback) { const number = Number(value); return Number.isFinite(number) && number > 0 ? number : fallback; }

module.exports = { BoundedQueue, SequenceTracker, RollingPcmBuffer };
