import { type Player } from "../buzz/index.ts"
import { join } from "path"
import type { Playback, StreamingPlayback } from "../buzz/utils.ts"
import { random } from "./index.ts"

export class WaitingSounds {
  typingPlayback?: Playback
  speakingPlayback?: Playback

  constructor(private player: Player, private streamPlayback: StreamingPlayback) {}

  async start() {
    if (this.typingPlayback) return // Already playing

    this.#startTypingSounds()
    this.#startSpeakingSounds()
  }

  async #startTypingSounds() {
    return new Promise<void>(async (resolve) => {
      do {
        const value = Math.random() * 100
        let dir: SoundDir
        if (value > 33) {
          dir = "typing"
        } else {
          dir = "clicking"
        }
        const typingSound = getSound(dir)
        this.typingPlayback = await this.player.play(typingSound)
        await this.typingPlayback.finished()
      } while (this.typingPlayback)

      resolve()
    })
  }

  async #startSpeakingSounds() {
    const playedSounds = new Set<string>()
    let dir: SoundDir | undefined
    return new Promise<void>(async (resolve) => {
      // Don't start speaking until the stream playback buffer is empty!
      while (this.streamPlayback.bufferEmptyFor < 1000) {
        await Bun.sleep(100)
      }

      do {
        this.streamPlayback.bufferEmptyFor
        const lastSoundDir = dir
        const value = Math.random() * 100
        if (lastSoundDir === "body-noises") {
          dir = "apology"
        } else if (value > 99 && !lastSoundDir) {
          dir = "body-noises"
        } else if (value > 75 && !lastSoundDir) {
          dir = "stalling"
        } else {
          dir = undefined
          await Bun.sleep(1000)
        }

        if (dir) {
          const speakingSound = getSound(dir, Array.from(playedSounds))
          this.speakingPlayback = await this.player.play(speakingSound)
          playedSounds.add(speakingSound)
          await this.speakingPlayback.finished()
        }
      } while (this.typingPlayback)
      resolve()
    })
  }

  async stop() {
    if (!this.typingPlayback) return

    await Promise.all([this.typingPlayback.stop(), this.speakingPlayback?.finished()])
    this.typingPlayback = undefined
  }
}

type SoundDir = (typeof soundDirs)[number]
const soundDirs = [
  "apology",
  "background",
  "body-noises",
  "clicking",
  "greeting",
  "stalling",
  "typing",
] as const
export const soundDir = join(import.meta.dir, "../../", "sounds")

export const getSound = (dir: SoundDir, exclude: string[] = []): string => {
  const glob = new Bun.Glob("*.wav")
  const soundPaths = Array.from(glob.scanSync({ cwd: join(soundDir, dir), absolute: true }))

  const filteredSoundPaths = soundPaths.filter((path) => !exclude.includes(path))
  if (filteredSoundPaths.length === 0) {
    return random(soundPaths)
  }

  return random(filteredSoundPaths)
}
