/**
 * Ring buffer binaire à offset monotone.
 * `totalOffset` compte tous les octets jamais écrits ; le ring ne garde que la
 * fenêtre récente. Un lecteur qui demande un offset sorti de la fenêtre reçoit
 * null et doit resync (reset terminal + queue du ring).
 */
export class RingBuffer {
  private buf: Buffer;
  private length = 0; // octets valides dans la fenêtre
  private total = 0; // offset monotone (Number : sûr jusqu'à 2^53)

  constructor(readonly capacity: number) {
    this.buf = Buffer.alloc(capacity);
  }

  get totalOffset(): number {
    return this.total;
  }

  get windowStart(): number {
    return this.total - this.length;
  }

  write(chunk: Buffer): void {
    // Chunk plus grand que la fenêtre : on ne stocke que la queue, mais en comptant
    // les octets sautés dans l'offset pour préserver l'invariant « l'octet k du flux
    // est à la position k % capacity » (sinon tail/readFrom rendent la fenêtre désordonnée).
    if (chunk.length >= this.capacity) {
      const skipped = chunk.length - this.capacity;
      this.total += skipped;
      chunk = chunk.subarray(skipped);
    }
    const writePos = this.total % this.capacity;
    const tailSpace = this.capacity - writePos;
    if (chunk.length <= tailSpace) {
      chunk.copy(this.buf, writePos);
    } else {
      chunk.copy(this.buf, writePos, 0, tailSpace);
      chunk.copy(this.buf, 0, tailSpace);
    }
    this.length = Math.min(this.capacity, this.length + chunk.length);
    this.total += chunk.length;
  }

  /** Octets depuis `offset` (inclus), ou null si l'offset est sorti de la fenêtre. */
  readFrom(offset: number): Buffer | null {
    if (offset > this.total) return null;
    if (offset === this.total) return Buffer.alloc(0);
    if (offset < this.windowStart) return null;
    const count = this.total - offset;
    return this.tail(count);
  }

  /** Les `count` derniers octets (bornés à la fenêtre), dans l'ordre. */
  tail(count: number): Buffer {
    const n = Math.min(count, this.length);
    const out = Buffer.alloc(n);
    const end = this.total % this.capacity;
    const start = (end - n + this.capacity * 2) % this.capacity;
    if (n === 0) return out;
    if (start < end || end === 0) {
      this.buf.copy(out, 0, start, start + n);
    } else {
      const firstPart = this.capacity - start;
      this.buf.copy(out, 0, start, this.capacity);
      this.buf.copy(out, firstPart, 0, end);
    }
    return out;
  }
}
