export type FfmpegVideoOption = "copy" | "h264" | "h265" | "exclude";
export type FfmpegAudioOption =
  "copy" | "aac" | "opus" | "pcmu" | "pcma" | "pcm" | "mp3" | "exclude";
export type FfmpegHardwareOption =
  "none" | "auto" | "vaapi" | "cuda" | "v4l2m2m" | "dxva2" | "videotoolbox";

export type ParsedFfmpegUrl = {
  isFfmpeg: boolean;
  baseUrl: string;
  // go2rtc accepts repeatable #video=/#audio= fragments to express a fallback
  // chain (copy if source codec matches, otherwise transcode). An empty array
  // means no fragment is emitted for that track — equivalent to "exclude".
  videos: FfmpegVideoOption[];
  audios: FfmpegAudioOption[];
  hardware: FfmpegHardwareOption;
  extraFragments: string[];
};

const VIDEO_VALUES = new Set(["copy", "h264", "h265"]);
const AUDIO_VALUES = new Set([
  "copy",
  "aac",
  "opus",
  "pcmu",
  "pcma",
  "pcm",
  "mp3",
]);
const HARDWARE_SPECIFIC = new Set([
  "vaapi",
  "cuda",
  "v4l2m2m",
  "dxva2",
  "videotoolbox",
]);

function isRecognizedFragment(frag: string): boolean {
  if (frag === "hardware") return true;
  if (frag.startsWith("video=")) return VIDEO_VALUES.has(frag.slice(6));
  if (frag.startsWith("audio=")) return AUDIO_VALUES.has(frag.slice(6));
  if (frag.startsWith("hardware=")) return HARDWARE_SPECIFIC.has(frag.slice(9));
  return false;
}

export function parseFfmpegUrl(url: string): ParsedFfmpegUrl {
  if (!url.startsWith("ffmpeg:")) {
    return {
      isFfmpeg: false,
      baseUrl: url,
      videos: [],
      audios: [],
      hardware: "none",
      extraFragments: [],
    };
  }

  const withoutPrefix = url.slice(7);
  const parts = withoutPrefix.split("#");
  const baseUrl = parts[0];
  const fragments = parts.slice(1);

  const videos: FfmpegVideoOption[] = [];
  const audios: FfmpegAudioOption[] = [];
  let hardware: FfmpegHardwareOption = "none";
  const extraFragments: string[] = [];

  for (const frag of fragments) {
    if (frag.startsWith("video=") && VIDEO_VALUES.has(frag.slice(6))) {
      videos.push(frag.slice(6) as FfmpegVideoOption);
    } else if (frag.startsWith("audio=") && AUDIO_VALUES.has(frag.slice(6))) {
      audios.push(frag.slice(6) as FfmpegAudioOption);
    } else if (frag === "hardware") {
      hardware = "auto";
    } else if (
      frag.startsWith("hardware=") &&
      HARDWARE_SPECIFIC.has(frag.slice(9))
    ) {
      hardware = frag.slice(9) as FfmpegHardwareOption;
    } else {
      extraFragments.push(frag);
    }
  }

  return {
    isFfmpeg: true,
    baseUrl,
    // Guarantee at least one row per track so the UI always has a primary
    // dropdown to render; "exclude" is the sentinel meaning "no fragment".
    videos: videos.length > 0 ? videos : ["exclude"],
    audios: audios.length > 0 ? audios : ["exclude"],
    hardware,
    extraFragments,
  };
}

// Splits the editable "base URL + extra fragments" portion of a compat-mode
// URL into its parts. Recognized fragments (video=, audio=, hardware) are
// dropped — they are managed by the dedicated controls in the UI.
export function parseFfmpegBaseAndExtras(input: string): {
  baseUrl: string;
  extraFragments: string[];
} {
  const cleaned = input.startsWith("ffmpeg:") ? input.slice(7) : input;
  const parts = cleaned.split("#");
  const baseUrl = parts[0];
  const extraFragments = parts.slice(1).filter((f) => !isRecognizedFragment(f));
  return { baseUrl, extraFragments };
}

export function buildFfmpegUrl(parsed: ParsedFfmpegUrl): string {
  let url = `ffmpeg:${parsed.baseUrl}`;

  // Exclude is a primary-row sentinel meaning "no fragment for this track" —
  // it's mutually exclusive with fallbacks. If the primary is exclude, emit
  // nothing for that track regardless of trailing entries.
  if (parsed.videos[0] !== "exclude") {
    for (const v of parsed.videos) {
      if (v === "exclude") continue;
      url += `#video=${v}`;
    }
  }
  if (parsed.audios[0] !== "exclude") {
    for (const a of parsed.audios) {
      if (a === "exclude") continue;
      url += `#audio=${a}`;
    }
  }
  if (parsed.hardware === "auto") {
    url += "#hardware";
  } else if (parsed.hardware !== "none") {
    url += `#hardware=${parsed.hardware}`;
  }
  for (const frag of parsed.extraFragments) {
    url += `#${frag}`;
  }

  return url;
}

export function toggleFfmpegMode(url: string, enable: boolean): string {
  if (enable) {
    if (url.startsWith("ffmpeg:")) {
      return url;
    }
    return `ffmpeg:${url}#video=copy#audio=copy`;
  }

  if (!url.startsWith("ffmpeg:")) {
    return url;
  }

  // Preserve unknown fragments (e.g. #timeout=10) when leaving compat mode;
  // only video/audio/hardware are go2rtc-ffmpeg directives that should be
  // dropped along with the prefix.
  const parsed = parseFfmpegUrl(url);
  return [parsed.baseUrl, ...parsed.extraFragments].join("#");
}