import {
  DEFAULT_AUDIO_FORMAT,
  type AudioFormat,
  type StreamingRecording,
  type FileRecording,
} from "./utils.js"
import { listDevices, findDeviceByLabel } from "./utils.js"

export class Recorder {
  #deviceId?: string
  #format: Required<AudioFormat>

  static async create({ label, format }: { label?: string; format?: AudioFormat } = {}) {
    const devices = await listDevices()
    const captureDevices = devices.filter((d) => d.type === "capture")

    if (captureDevices.length === 0) {
      throw new Error("No capture devices found")
    }

    let deviceId: string | undefined
    if (label) {
      const device = await findDeviceByLabel(label, "capture")
      deviceId = device.id
    }

    return new Recorder({ deviceId, format })
  }

  constructor({ deviceId, format }: { deviceId?: string; format?: AudioFormat }) {
    this.#deviceId = deviceId
    this.#format = {
      format: format?.format ?? DEFAULT_AUDIO_FORMAT.format,
      sampleRate: format?.sampleRate ?? DEFAULT_AUDIO_FORMAT.sampleRate,
      channels: format?.channels ?? DEFAULT_AUDIO_FORMAT.channels,
    }
  }

  #commonArgs(): string[] {
    const args = []
    if (this.#deviceId) {
      args.push("-D", this.#deviceId)
    }
    args.push(
      "-f",
      this.#format.format,
      "-r",
      this.#format.sampleRate.toString(),
      "-c",
      this.#format.channels.toString()
    )
    return args
  }

  #startStreaming(): StreamingRecording {
    const args = [...this.#commonArgs(), "-t", "raw", "-"]

    const proc = Bun.spawn(["arecord", ...args], {
      stdout: "pipe",
      stderr: "pipe",
    })

    const handle: StreamingRecording = {
      isRecording: true,
      stream: () => proc.stdout,
      stop: async () => {
        if (!handle.isRecording) return
        handle.isRecording = false
        proc.kill()
        await proc.exited
      },
    }

    proc.exited.then(() => {
      handle.isRecording = false
    })

    return handle
  }

  #startFileRecording(outputFile: string): FileRecording {
    const args = [...this.#commonArgs(), "-t", "wav", outputFile]

    const proc = Bun.spawn(["arecord", ...args], {
      stdout: "pipe",
      stderr: "pipe",
    })

    const handle: FileRecording = {
      isRecording: true,
      stop: async () => {
        if (!handle.isRecording) return
        handle.isRecording = false
        proc.kill()
        await proc.exited
      },
    }

    proc.exited.then(() => {
      handle.isRecording = false
    })

    return handle
  }

  start(): StreamingRecording
  start(outputFile: string): FileRecording
  start(outputFile?: string): StreamingRecording | FileRecording {
    if (outputFile) {
      return this.#startFileRecording(outputFile)
    }
    return this.#startStreaming()
  }
}
