import type { ChildProcessWithoutNullStreams } from "node:child_process";
import { spawn as nodeSpawn } from "node:child_process";
import { StreamType } from "@discordjs/voice";
import { discordPlayer } from "../player";
import type {
  DiscordAudioPlayer,
  MusicPlayback,
  MusicPlayer,
  ResolvedMediaSource,
} from "./mediaTypes";

export interface MusicPlayerDependencies {
  spawn?: typeof nodeSpawn;
  discordPlayer?: DiscordAudioPlayer;
}

export function createMusicPlayer(
  dependencies: MusicPlayerDependencies = {},
): MusicPlayer {
  const spawn = dependencies.spawn ?? nodeSpawn;
  const audioPlayer = dependencies.discordPlayer ?? discordPlayer;

  return {
    play(source: ResolvedMediaSource): MusicPlayback {
      if (!audioPlayer.isConnected()) {
        throw new Error("Discord audio player is not connected");
      }

      const proc = spawn("ffmpeg", buildFfmpegArgs(source.source), {
        stdio: ["ignore", "pipe", "pipe"],
      }) as unknown as ChildProcessWithoutNullStreams;
      proc.stderr.resume();

      audioPlayer.playStream(proc.stdout, "music", {
        inputType: StreamType.Raw,
        inlineVolume: true,
      });

      let stopped = false;
      let released = false;
      const release = () => {
        if (released) return;
        released = true;
        audioPlayer.stop("music");
      };

      const done = new Promise<void>((resolve, reject) => {
        proc.on("error", (error) => {
          release();
          reject(error);
        });
        proc.stdout.on("error", (error) => {
          release();
          reject(error);
        });
        proc.on("close", (code) => {
          release();
          if (code === 0 || stopped) {
            resolve();
            return;
          }
          reject(new Error(`ffmpeg exited with code ${code}`));
        });
      });

      return {
        done,
        stop() {
          if (stopped) return;
          stopped = true;
          proc.kill("SIGTERM");
          release();
        },
      };
    },
  };
}

export function buildFfmpegArgs(source: string): string[] {
  return [
    "-hide_banner",
    "-loglevel",
    "warning",
    "-i",
    source,
    "-vn",
    "-acodec",
    "pcm_s16le",
    "-ar",
    "48000",
    "-ac",
    "2",
    "-f",
    "s16le",
    "pipe:1",
  ];
}
