const { EventEmitter } = require("events");
const { existsSync } = require("fs");
const path = require("path");
const { execSync, spawn } = require("child_process");
const logger = require("./logger");

class AudioPlayer extends EventEmitter {
  constructor(options) {
    super(options);
    this.options = options;
    this.audioList = [];
    this.ensurePulse();
    this.getSpeakerList();
    this.playing = false;
  }

  ensurePulse() {
    try {
      execSync("LANG=en pactl info");
      this.pactlAvailable = true;
      logger.info("PulseAudio/PipeWire 可用");
    } catch (e) {
      this.pactlAvailable = false;
      logger.warn("pactl 不可用，音频设备列表可能不可用");
    }
  }

  stopAll() {
    this.audioList.forEach((item) => item.stop && item.stop());
    this.audioList = [];
  }

  async playFile(filePath, stop = false) {
    if (stop) {
      this.stopAll();
    }
    if (!filePath) return;
    if (!existsSync(filePath)) {
      logger.info(`${filePath} 不存在`);
      this.playing = false;
      return;
    }
    const filename = path.posix.basename(filePath);
    const wavPath = `/tmp/${filename.replace("mp3", "wav")}`;
    if (!existsSync(wavPath)) {
      const cmd = `ffmpeg -v warning -i "${filePath}" -f wav "${wavPath}"`;
      logger.info("mp3 转换 cmd: " + cmd);
      try {
        execSync(cmd);
      } catch (e) {
        logger.info("mp3 转换失败: " + e.message);
      }
    }
    return this.playWav(wavPath);
  }

  playWav(wavPath) {
    try {
      if (!existsSync(wavPath)) return;
      const player = spawn("paplay", [wavPath]);
      player.on("close", () => {
        logger.info("paplay close");
      });
      const audio = {
        stop() {
          player.kill("SIGHUP");
        },
        end: new Promise((resolve) => {
          player.on("close", resolve);
        }),
      };
      this.audioList.push(audio);
      audio.end.then(() => {
        this.audioList = this.audioList.filter((item) => item !== audio);
      });
      return audio;
    } catch (e) {
      logger.info("Play wav error: " + e.message);
    }
  }

  createStreamPlayer({
    sampleRate,
    channelCount,
    bitsPerSample,
    label = "Audio stream player",
  }) {
    console.info("createStreamPlayer", {
      label,
      sampleRate,
      channelCount,
      bitsPerSample,
    });

    const format = `s${bitsPerSample}le`;
    const player = spawn("pacat", [
      "--playback",
      `--format=${format}`,
      `--rate=${sampleRate}`,
      `--channels=${channelCount}`,
    ]);

    player.on("close", () => {
      logger.info(`${label} close`);
    });

    return {
      write: (data) => player.stdin.write(data),
      stop: () => player.kill("SIGHUP"),
      end: () => player.stdin.end(),
    };
  }

  /**
   * 获取音频播放设备列表
   */
  async getSpeakerList() {
    if (!this.pactlAvailable) {
      logger.warn("pactl 不可用，跳过获取音频设备列表");
      this.list = [];
      return [];
    }

    try {
      const output = execSync("LANG=en pactl list sinks").toString();
      const matchList = Array.from(
        output.matchAll(
          /Sink #(\d+)[\s\S]+?Name: (.+)[\s\S]+?Description: (.+)[\s\S]+?Volume: [\s\S]+?(\d+)\%/g
        )
      );

      const list = matchList.map(([, index, name, description, volume]) => ({
        index,
        displayName: description,
        name,
        volume,
        description,
      }));

      logger.info("获取音频设备列表", list);
      this.list = list;
      return list;
    } catch (e) {
      logger.warn("获取音频设备列表失败: " + e.message);
      this.list = [];
      return [];
    }
  }

  // 获取当前播放设备
  async getSpeaker() {
    if (!this.pactlAvailable) {
      logger.warn("pactl 不可用，无法获取当前播放设备");
      return undefined;
    }

    try {
      const output = execSync("LANG=en pactl info").toString();
      const [, name] = output.match(/Default Sink: (.+?)\n/) || [];
      logger.info("获取当前播放设备", name);
      const speaker = (await this.getSpeakerList()).find(
        (item) => item.name === name
      );
      logger.info("获取当前播放设备", speaker);
      return speaker;
    } catch (e) {
      logger.warn("获取当前播放设备失败: " + e.message);
      return undefined;
    }
  }

  // 设置音频播放设备
  async setSpeaker(name) {
    if (!this.pactlAvailable) {
      logger.warn("pactl 不可用，无法设置音频设备");
      return;
    }
    logger.info("设置音频设备", name);
    execSync(`LANG=en pactl set-default-sink ${name}`);
  }

  // 设置音频播放设备音量
  async setSpeakerVolume(name, v) {
    if (!this.pactlAvailable) {
      logger.warn("pactl 不可用，无法设置音量");
      return;
    }
    logger.info("设置音频设备音量", name, v);
    execSync(`LANG=en pactl set-sink-volume ${name} ${v}%`);
  }

  destroy() {
    this.stopAll();
  }
}

module.exports = new AudioPlayer();
