/**
 * StreamBuffer — smooth token-by-token rendering for LLM streams.
 *
 * Network delivers tokens in bursts (5-10 at once, then a pause).
 * This buffer queues incoming text and drains it at a steady rate
 * via requestAnimationFrame, creating the smooth "typing" effect
 * seen in ChatGPT / Claude.ai.
 */
export class StreamBuffer {
  /**
   * @param {(text: string) => void} onText  — called each frame with chars to append
   * @param {() => void} onDone              — called when buffer fully drained after finish()
   */
  constructor(onText, onDone) {
    this.queue = ''
    this.onText = onText
    this.onDone = onDone
    this.rafId = null
    this.finished = false       // stream has ended, flush remaining
    this.baseSpeed = 3          // chars per frame at 60fps (~180 chars/sec)
  }

  /** Push new text from a stream chunk into the buffer. */
  push(text) {
    this.queue += text
    if (!this.rafId) {
      this.startDrain()
    }
  }

  /** Signal that the stream is complete — flush remaining text quickly. */
  finish() {
    this.finished = true
    if (!this.rafId && this.queue.length > 0) {
      this.startDrain()
    }
  }

  /** Cancel the buffer (e.g. room switch). */
  cancel() {
    if (this.rafId) {
      cancelAnimationFrame(this.rafId)
      this.rafId = null
    }
    this.queue = ''
    this.finished = false
  }

  /** @private */
  startDrain() {
    this.rafId = requestAnimationFrame(() => this.drain())
  }

  /** @private */
  drain() {
    if (this.queue.length === 0) {
      this.rafId = null
      if (this.finished) {
        this.onDone()
      }
      return
    }

    // Adaptive speed:
    //  - Base: ~3 chars/frame (smooth typing feel)
    //  - If buffer > 50 chars: speed up to avoid falling behind
    //  - If stream ended: flush fast (10+ chars/frame)
    let chars = this.baseSpeed

    if (this.queue.length > 200) {
      // Very behind — catch up aggressively
      chars = Math.ceil(this.queue.length / 10)
    } else if (this.queue.length > 50) {
      // Moderately behind — speed up proportionally
      chars = Math.ceil(this.queue.length / 15)
    }

    if (this.finished) {
      // Stream done, flush remaining smoothly but quickly
      chars = Math.max(chars, Math.ceil(this.queue.length / 8))
    }

    // Don't break in the middle of a multi-byte character or word
    // (slice is safe for most LLM output which is ASCII/simple unicode)
    const chunk = this.queue.slice(0, chars)
    this.queue = this.queue.slice(chars)

    this.onText(chunk)

    this.rafId = requestAnimationFrame(() => this.drain())
  }
}
