import assert from 'assert';
import { logger } from '../logger/index.ts';
import { ChildProcessWithoutNullStreams, spawn } from 'child_process';
import recorders from './recorders/index.ts';
import Stream from 'stream';
export type RecordingOptions = {
  /* 采样率，默认为16000 */
  sampleRate?: number;
  /* 声道数，默认为1 */
  channels?: number;
  /* 是否压缩音频，默认为false */
  compress?: boolean;
  /* 录音开始的音量阈值，默认为0.5 */
  threshold?: number;
  /* 开始录音的音量阈值 */
  thresholdStart?: number;
  /* 结束录音的音量阈值 */
  thresholdEnd?: number;
  /* 录音结束的静默时间，默认为'1.0'秒 */
  silence?: string;
  /* 使用的录音器，默认为'sox' */
  recorder?: string;
  /* 是否在静默时结束录音，默认为false */
  endOnSilence?: boolean;
  /* 音频类型，默认为'wav' */
  audioType?: string;
};
/**
 * node-record-lpcm16
 * https://github.com/gillesdemey/node-record-lpcm16
 */
export class Recording {
  options: RecordingOptions;
  cmd: string;
  args: string[];
  cmdOptions: any;
  process: ChildProcessWithoutNullStreams;
  _stream: Stream.Readable;

  constructor(options?: RecordingOptions) {
    const defaults = {
      sampleRate: 16000,
      channels: 1,
      compress: false,
      threshold: 0.5,
      thresholdStart: null,
      thresholdEnd: null,
      silence: '1.0',
      recorder: 'sox',
      endOnSilence: false,
      audioType: 'wav',
    };

    this.options = Object.assign(defaults, options);

    const recorder = recorders[this.options.recorder];
    if (!recorder) {
      throw new Error(`No such recorder found: ${this.options.recorder}`);
    }
    const { cmd, args, spawnOptions = {} } = recorder(this.options);

    this.cmd = cmd;
    this.args = args;
    this.cmdOptions = Object.assign({ encoding: 'binary', stdio: 'pipe' }, spawnOptions);

    logger.debug(`Started recording`);
    logger.debug('options', this.options);
    logger.debug(` ${this.cmd} ${this.args.join(' ')}`);

    return this.start();
  }

  start() {
    const { cmd, args, cmdOptions } = this;

    const cp = spawn(cmd, args, cmdOptions);
    const rec = cp.stdout;
    const err = cp.stderr;

    this.process = cp; // expose child process
    this._stream = rec; // expose output stream

    cp.on('close', (code) => {
      if (code === 0) return;
      rec.emit(
        'error',
        `${this.cmd} has exited with error code ${code}.

Enable debugging with the environment variable DEBUG=record.`,
      );
    });

    err.on('data', (chunk) => {
      logger.debug(`STDERR: ${chunk}`);
    });

    rec.on('data', (chunk) => {
      logger.debug(`Recording ${chunk.length} bytes`);
    });

    rec.on('end', () => {
      logger.debug('Recording ended');
    });

    return this;
  }

  stop() {
    assert(this.process, 'Recording not yet started');

    this.process.kill();
  }

  pause() {
    assert(this.process, 'Recording not yet started');

    this.process.kill('SIGSTOP');
    this._stream.pause();
    logger.debug('Paused recording');
  }

  resume() {
    assert(this.process, 'Recording not yet started');

    this.process.kill('SIGCONT');
    this._stream.resume();
    logger.debug('Resumed recording');
  }

  isPaused() {
    assert(this.process, 'Recording not yet started');

    return this._stream.isPaused();
  }

  stream() {
    assert(this._stream, 'Recording not yet started');

    return this._stream;
  }
}

export const record = (...args) => new Recording(...args);
