import fs from "fs";
import path from "path";
import { buildMuxFfmpegArgs, runFfmpeg } from "./audio/ffmpegProcess";

const recordingsDir = process.env.RECORDINGS_DIR ?? "./recordings";

interface EventMetadata {
  userId: string;
  username: string;
  tag: string;
  displayName?: string;
  avatarUrl?: string;
  bot?: boolean;
  roles?: Array<{ id: string; name: string; position: number }>;
  highestRole?: { id: string; name: string; position: number } | null;
  joinedTimestamp?: number | null;
  sessionId?: string;
  sessionStartTime?: number;
  segmentIndex?: number;
  segmentMs?: number;
  startTime: number;
  endTime: number;
  durationMs: number;
  filename: string;
}

interface ClipInfo {
  oggPath: string;
  jsonPath: string;
  meta: EventMetadata;
}

async function startMuxing() {
  console.log("[muxer] Scanning recordings directory...");
  if (!fs.existsSync(recordingsDir)) {
    console.error("[muxer] Recordings directory not found.");
    return;
  }

  const clips: ClipInfo[] = [];

  // Scan user directories
  const items = fs.readdirSync(recordingsDir);
  console.log(`[muxer] Found ${items.length} directories to scan...`);

  let processedDirs = 0;
  for (const item of items) {
    const itemPath = path.join(recordingsDir, item);
    if (fs.statSync(itemPath).isDirectory()) {
      const files = fs.readdirSync(itemPath);
      for (const file of files) {
        if (file.endsWith(".json")) {
          const jsonPath = path.join(itemPath, file);
          const oggPath = jsonPath.replace(/\.json$/, ".ogg");
          if (fs.existsSync(oggPath)) {
            try {
              // Check if OGG file is valid (not empty and has reasonable size)
              const oggStats = fs.statSync(oggPath);
              if (oggStats.size === 0) {
                console.warn(`[muxer] Skipping empty OGG file: ${oggPath}`);
                continue;
              }

              // Skip files that are too small (less than 1KB likely corrupted)
              if (oggStats.size < 1024) {
                console.warn(
                  `[muxer] Skipping too small OGG file (${oggStats.size} bytes): ${oggPath}`,
                );
                continue;
              }

              // Check if OGG file has valid header (starts with "OggS")
              const oggBuffer = fs.readFileSync(oggPath);
              const oggHeader = oggBuffer.subarray(0, 4).toString();
              if (oggHeader !== "OggS") {
                console.warn(
                  `[muxer] Skipping invalid OGG file (bad header): ${oggPath}`,
                );
                continue;
              }

              const meta: EventMetadata = JSON.parse(
                fs.readFileSync(jsonPath, "utf-8"),
              );
              clips.push({ oggPath, jsonPath, meta });
            } catch (e) {
              console.error(
                `[muxer] Failed to read/parse JSON: ${jsonPath}`,
                e,
              );
            }
          }
        }
      }
      processedDirs++;
      const progress = ((processedDirs / items.length) * 100).toFixed(2);
      console.log(
        `[muxer] Scanning progress: ${progress}% (${processedDirs}/${items.length} directories)`,
      );
    }
  }

  if (clips.length === 0) {
    console.log("[muxer] No recording clips found to mux.");
    return;
  }

  // Sort by startTime so chronologically they are in order
  clips.sort((a, b) => a.meta.startTime - b.meta.startTime);

  // Find the global start time
  const globalStartTime = clips[0].meta.startTime;
  console.log(
    `[muxer] Found ${clips.length} clips. Base timestamp: ${globalStartTime}`,
  );

  const filterParts: string[] = [];

  console.log(`[muxer] Creating audio filters for ${clips.length} clips...`);
  clips.forEach((clip, index) => {
    // Calculate delay relative to the global start time
    const delayMs = clip.meta.startTime - globalStartTime;

    // FFmpeg filter structure: [0:a]adelay=1000|1000[a0]
    // Setting adelay multiple times covers stereo channels.
    // We ensure all multiple channels get delayed.
    const inputSpecifier = `[${index}:a]`;
    const outputSpecifier = `[pad${index}]`;

    filterParts.push(
      `${inputSpecifier}adelay=${delayMs}|${delayMs}${outputSpecifier}`,
    );

    const progress = (((index + 1) / clips.length) * 100).toFixed(2);
    console.log(
      `[muxer] Filter creation progress: ${progress}% (${index + 1}/${clips.length} clips)`,
    );
  });

  // Merge them using amix
  const amixInputs = clips.map((_, i) => `[pad${i}]`).join("");
  // We add the amix command. dropout_transition=0 avoids volume drop when streams end.
  filterParts.push(
    `${amixInputs}amix=inputs=${clips.length}:dropout_transition=0[out]`,
  );

  const outputFilename = path.join(recordingsDir, `muxed-${Date.now()}.mp3`);
  const inputs = clips.map((clip) => clip.oggPath);

  console.log(`[muxer] Combining clips. This might take a while...`);

  try {
    const args = buildMuxFfmpegArgs({
      inputs,
      filter: filterParts.join(";"),
      output: outputFilename,
      codec: "libmp3lame",
    });

    await runFfmpeg(args);
    console.log(
      `[muxer] Successfully muxed! Output saved to: ${outputFilename}`,
    );
  } catch (err) {
    console.error(`[muxer] FFmpeg Error:`, err);
  }
}

startMuxing();
