import type { WsLike, TimerLike } from '../../src/transport/seams.js'

/** In-memory WsLike double: records sent frames, lets tests emit inbound events. */
export class FakeWs implements WsLike {
  readonly sent: Uint8Array[] = []
  closed = false
  private readonly listeners = new Map<string, Array<(...a: unknown[]) => void>>()

  send(d: Uint8Array): void {
    this.sent.push(d)
  }
  on(ev: 'message' | 'close' | 'error', cb: (...a: unknown[]) => void): void {
    const arr = this.listeners.get(ev) ?? []
    arr.push(cb)
    this.listeners.set(ev, arr)
  }
  close(): void {
    this.closed = true
    this.emit('close')
  }
  emit(ev: string, ...args: unknown[]): void {
    for (const cb of this.listeners.get(ev) ?? []) cb(...args)
  }
  emitMessage(bytes: Uint8Array): void {
    this.emit('message', bytes)
  }
}

/** Deterministic controllable timer for heartbeat/rotation tests. */
export class FakeTimer implements TimerLike {
  private seq = 0
  private readonly timeouts = new Map<number, { cb: () => void; ms: number }>()
  private readonly intervals = new Map<number, { cb: () => void; ms: number }>()

  setTimeout(cb: () => void, ms: number): unknown {
    const id = this.seq++
    this.timeouts.set(id, { cb, ms })
    return id
  }
  clearTimeout(handle: unknown): void {
    this.timeouts.delete(handle as number)
  }
  setInterval(cb: () => void, ms: number): unknown {
    const id = this.seq++
    this.intervals.set(id, { cb, ms })
    return id
  }
  clearInterval(handle: unknown): void {
    this.intervals.delete(handle as number)
  }
  /** Fire every armed timeout whose delay is ≤ ms (single shot) and every interval once. */
  advance(ms: number): void {
    for (const [id, t] of [...this.timeouts]) {
      if (t.ms <= ms) {
        this.timeouts.delete(id)
        t.cb()
      }
    }
    for (const t of [...this.intervals.values()]) {
      if (t.ms <= ms) t.cb()
    }
  }
}
