// src/transcoder.ts
// FFmpeg + HLS logic – identical behaviour to the VPS transcoder.
// Keeps all preset values, FFmpeg flags, and playlist structure unchanged
// so output is fully compatible with the existing HLS player.

import { spawn } from "child_process";
import * as fs from "fs/promises";
import * as path from "path";

// ---------------------------------------------------------------------------
// Presets – must stay in sync with the VPS transcoder.
// ---------------------------------------------------------------------------

const HLS_PRESETS = [
  { name: "1080p", width: 1920, height: 1080, bitrate: "3500k", maxrate: "4000k" },
  { name: "720p",  width: 1280, height: 720,  bitrate: "1800k", maxrate: "2000k" },
  { name: "480p",  width: 854,  height: 480,  bitrate: "900k",  maxrate: "1000k"  },
] as const;

type HlsPreset = (typeof HLS_PRESETS)[number];

// ---------------------------------------------------------------------------
// Subprocess helpers
// ---------------------------------------------------------------------------

function runFFmpeg(args: string[]): Promise<void> {
  return new Promise((resolve, reject) => {
    const proc = spawn("ffmpeg", args, { stdio: "inherit" });
    proc.on("error", reject);
    proc.on("close", (code) => {
      if (code === 0) resolve();
      else reject(new Error(`FFmpeg exited with code ${code}`));
    });
  });
}

export async function probeResolution(
  input: string
): Promise<{ width: number; height: number }> {
  return new Promise((resolve, reject) => {
    const proc = spawn("ffprobe", [
      "-v",              "error",
      "-select_streams", "v:0",
      "-show_entries",   "stream=width,height",
      "-of",             "csv=s=x:p=0",
      input,
    ]);

    let output = "";
    proc.stdout.on("data", (d: Buffer) => { output += d.toString(); });
    proc.stderr.on("data", () => {/* suppress */});

    proc.on("error", reject);
    proc.on("close", (code) => {
      if (code !== 0) return reject(new Error(`ffprobe exited with code ${code}`));
      const parts = output.trim().split("x").map(Number);
      if (parts.length !== 2 || !parts[0] || !parts[1]) {
        return reject(new Error(`Could not parse resolution: "${output.trim()}"`));
      }
      resolve({ width: parts[0], height: parts[1] });
    });
  });
}

// ---------------------------------------------------------------------------
// Per-variant HLS stream
// ---------------------------------------------------------------------------

async function createVariant(
  input: string,
  outputDir: string,
  preset: HlsPreset
): Promise<void> {
  const segmentPattern = path.join(outputDir, `${preset.name}_%03d.ts`);
  const playlistPath   = path.join(outputDir, `${preset.name}.m3u8`);

  const args = [
    "-y",
    "-i",              input,
    "-c:v",            "libx264",
    "-preset",         "medium",
    "-profile:v",      "main",
    "-crf",            "20",
    "-vf",             `scale=w='min(${preset.width},iw)':h='min(${preset.height},ih)':force_original_aspect_ratio=decrease:force_divisible_by=2`,
    "-b:v",            preset.bitrate,
    "-maxrate",        preset.maxrate,
    "-bufsize",        "4000k",
    "-c:a",            "aac",
    "-b:a",            "128k",
    "-f",              "hls",
    "-hls_time",       "6",
    "-hls_playlist_type", "vod",
    "-hls_flags",      "independent_segments",
    "-hls_segment_filename", segmentPattern,
    playlistPath,
  ];

  console.log(`[HLS] Creating ${preset.name}`);
  await runFFmpeg(args);
}

// ---------------------------------------------------------------------------
// Master playlist
// ---------------------------------------------------------------------------

async function createMasterPlaylist(
  outputDir: string,
  variants: readonly string[]
): Promise<void> {
  const lines: string[] = ["#EXTM3U", "#EXT-X-VERSION:3"];

  for (const name of variants) {
    const preset = HLS_PRESETS.find((p) => p.name === name)!;
    const bandwidth = parseInt(preset.maxrate.replace("k", ""), 10) * 1000;
    lines.push(
      `#EXT-X-STREAM-INF:BANDWIDTH=${bandwidth},RESOLUTION=${preset.width}x${preset.height}`
    );
    lines.push(`${preset.name}.m3u8`);
  }

  await fs.writeFile(path.join(outputDir, "master.m3u8"), lines.join("\n"));
}

// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------

export interface TranscodeResult {
  /** Names of variants that were generated, e.g. ["1080p","720p","480p"]. */
  variants: string[];
}

/**
 * Transcode inputPath to HLS inside outputDir.
 * outputDir will be created if it does not exist.
 * Behaviour is identical to the VPS transcoder.
 */
export async function transcodeToHls(
  inputPath: string,
  outputDir: string
): Promise<TranscodeResult> {
  const { width, height } = await probeResolution(inputPath);
  console.log(`[Probe] Resolution: ${width}x${height}`);

  const allowed = HLS_PRESETS.filter(
    (p) => p.width <= width && p.height <= height
  );

  if (allowed.length === 0) {
    throw new Error(
      `No suitable HLS variants for source resolution ${width}x${height}`
    );
  }

  await fs.mkdir(outputDir, { recursive: true });

  for (const preset of allowed) {
    await createVariant(inputPath, outputDir, preset);
  }

  const variantNames = allowed.map((p) => p.name);
  await createMasterPlaylist(outputDir, variantNames);

  return { variants: variantNames };
}
