import Client from "../Client";

// Available output containers (must match backend)
export const FORMAT_EXTS = ["mp4", "mkv", "webm", "flv", "mov", "avi", "ogg"] as const;
export type FormatExt = (typeof FORMAT_EXTS)[number];

export type InfoResponse = {
  title: string | null;
  duration: number | null;
  thumbnail: string | null;
  video_resolutions: string[];   // e.g. ["2160p", "1440p", "1080p", ...]
  audio_resolutions: string[];   // e.g. ["320kbps", "160kbps", ...]
};

// GET info for a URL
export async function fetchInfo(url: string): Promise<InfoResponse> {
  const res = await Client.public.get("/api/downloader/download/", {
    params: { url },
  });
  return res.data as InfoResponse;
}

// POST to stream binary immediately; returns { blob, filename }
export async function downloadImmediate(args: {
  url: string;
  ext: FormatExt;
  videoResolution?: string | number; // "1080p" or 1080
  audioResolution?: string | number; // "160kbps" or 160
}): Promise<{ blob: Blob; filename: string }> {
  const video_quality = toHeight(args.videoResolution);
  const audio_quality = toKbps(args.audioResolution);

  if (video_quality == null || audio_quality == null) {
    throw new Error("Please select both video and audio quality.");
  }

  const res = await Client.public.post(
    "/api/downloader/download/",
    {
      url: args.url,
      ext: args.ext,
      video_quality,
      audio_quality,
    },
    { responseType: "blob" }
  );

  const cd = res.headers?.["content-disposition"] as string | undefined;
  const xfn = res.headers?.["x-filename"] as string | undefined;
  const filename =
    parseContentDispositionFilename(cd) ||
    (xfn && xfn.trim()) ||
    inferFilenameFromUrl(args.url, res.headers?.["content-type"] as string | undefined) ||
    `download.${args.ext}`;

  return { blob: res.data as Blob, filename };
}

// Helpers
export function parseContentDispositionFilename(cd?: string): string | null {
  if (!cd) return null;
  const utf8Match = cd.match(/filename\*\s*=\s*UTF-8''([^;]+)/i);
  if (utf8Match?.[1]) return decodeURIComponent(utf8Match[1]);
  const plainMatch = cd.match(/filename\s*=\s*"([^"]+)"/i) || cd.match(/filename\s*=\s*([^;]+)/i);
  return plainMatch?.[1]?.trim() || null;
}

function inferFilenameFromUrl(url: string, contentType?: string): string {
  try {
    const u = new URL(url);
    const last = u.pathname.split("/").filter(Boolean).pop();
    if (last) return last;
  } catch {
    // ignore
  }
  if (contentType) {
    const ext = contentTypeToExt(contentType);
    return `download${ext ? `.${ext}` : ""}`;
  }
  return "download.bin";
}

function contentTypeToExt(ct?: string): string | null {
  if (!ct) return null;
  const map: Record<string, string> = {
    "video/mp4": "mp4",
    "video/x-matroska": "mkv",
    "video/webm": "webm",
    "video/x-flv": "flv",
    "video/quicktime": "mov",
    "video/x-msvideo": "avi",
    "video/ogg": "ogg",
    "application/octet-stream": "bin",
  };
  return map[ct] || null;
}

function toHeight(v?: string | number): number | undefined {
  if (typeof v === "number") return v || undefined;
  if (!v) return undefined;
  const m = /^(\d{2,4})p$/i.exec(v.trim());
  if (m) return parseInt(m[1], 10);
  const n = Number(v);
  return Number.isFinite(n) ? (n as number) : undefined;
}

function toKbps(v?: string | number): number | undefined {
  if (typeof v === "number") return v || undefined;
  if (!v) return undefined;
  const m = /^(\d{2,4})\s*kbps$/i.exec(v.trim());
  if (m) return parseInt(m[1], 10);
  const n = Number(v);
  return Number.isFinite(n) ? (n as number) : undefined;
}
